dsh-vscode-mode 0.1.35 → 0.1.36
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/client.js +389 -128
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/paths.ts +2 -0
- package/src/client/state/workspaceFoldCache.ts +95 -0
- package/src/client/styles/mcp.css +35 -12
- package/src/client/ui/KeybindingsPanel.ts +8 -5
- package/src/client/ui/LspSettings.ts +14 -8
- package/src/client/ui/McpSettings.ts +110 -49
- package/src/client/ui/PerfSettings.ts +145 -34
package/lib/client.js
CHANGED
|
@@ -1861,7 +1861,9 @@ window.__ModuleLoader__.load({
|
|
|
1861
1861
|
/** 搜索面板条件(SearchPanel v1)。 */
|
|
1862
1862
|
search: "edrv.search.v1.",
|
|
1863
1863
|
/** 侧边栏提示已关闭标记(全局,无会话)。 */
|
|
1864
|
-
sideHint: "edrv.side-hint-dismissed"
|
|
1864
|
+
sideHint: "edrv.side-hint-dismissed",
|
|
1865
|
+
/** 「性能优化」页工作区栏目展开状态(workspaceFoldCache v1,全局不按会话)。 */
|
|
1866
|
+
workspaceFold: "edrv.ws-fold.v1."
|
|
1865
1867
|
};
|
|
1866
1868
|
//#endregion
|
|
1867
1869
|
//#region src/client/state/viewStateCache.ts
|
|
@@ -4230,7 +4232,7 @@ window.__ModuleLoader__.load({
|
|
|
4230
4232
|
}
|
|
4231
4233
|
//#endregion
|
|
4232
4234
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
|
|
4233
|
-
const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* 子 Tab:我的 MCP / MCP 市场 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:10px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0 0 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-moved{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-moved h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-config{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-config h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-guide{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:12px 14px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-guide h3{margin:0 0 8px;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
|
|
4235
|
+
const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-subtabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 顶层 Tab:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 统一线框面板:圆角矩形边框圈住各设置分区,提升可读性。\n 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row:last-child{border-bottom:0}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性条目(面板标题改用 .vsm-panel-title) */\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* keyboard shortcuts page */\n.vsm-kb-list{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-kb-row{display:flex;align-items:center;gap:10px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:8px;background:var(--dsw-alias-bg-layer-1,#161616)}\n.vsm-kb-row.rec{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-kb-label{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px;color:var(--dsw-alias-label-primary,#eee);font-size:13px}\n.vsm-kb-warn{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px}\n.vsm-kb-key{min-width:96px;text-align:center;padding:3px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-kb-key.warn{border-color:var(--dsw-alias-state-warn-primary,#e5b93f);color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-key.invalid{border-color:var(--dsw-alias-state-error-primary,#d9534f);color:var(--dsw-alias-state-error-primary,#d9534f)}\n.vsm-kb-rec{font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-kb-actions{display:flex;gap:6px}\n.vsm-kb-actions button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:4px 10px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-kb-actions button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-kb-actions button:disabled{opacity:.55;cursor:default}\n.vsm-kb-actionsbar{display:flex;align-items:center;gap:10px;margin-top:12px}\n.vsm-kb-dirty{flex:1;font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-kb-message{font-size:12px;color:var(--dsw-alias-brand-primary,#4f8cff);margin-top:8px}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
|
|
4234
4236
|
const tagId = "dsh-vscode-mode/mcp.css";
|
|
4235
4237
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
4236
4238
|
const tag = document.createElement("style");
|
|
@@ -4617,7 +4619,7 @@ window.__ModuleLoader__.load({
|
|
|
4617
4619
|
onClick: () => resetRow(command.id)
|
|
4618
4620
|
}, "恢复"))));
|
|
4619
4621
|
});
|
|
4620
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("div", { className: "vsm-kb-list" }, rows), react.default.createElement("div", { className: "vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
|
|
4622
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "快捷键"), react.default.createElement("p", null, "配置编辑器视图的快捷键(保存 / 快速打开 / 侧边栏 / 搜索)。修改即时写入配置,重新聚焦编辑器后生效。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用默认键位。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), message && react.default.createElement("div", { className: "vsm-kb-message" }, message), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "编辑器键位"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-kb-list" }, rows)), react.default.createElement("div", { className: "vsm-panel-body vsm-kb-actionsbar" }, react.default.createElement("span", { className: "vsm-kb-dirty" }, dirty ? "有未保存的修改" : ""), react.default.createElement("button", {
|
|
4621
4623
|
className: "vsm-primary vsm-small",
|
|
4622
4624
|
disabled: unavailable || notReady || busy || snapshot?.writable === false || !dirty,
|
|
4623
4625
|
onClick: save
|
|
@@ -4625,7 +4627,7 @@ window.__ModuleLoader__.load({
|
|
|
4625
4627
|
className: "vsm-small",
|
|
4626
4628
|
disabled: unavailable || notReady || busy || snapshot?.writable === false,
|
|
4627
4629
|
onClick: resetAll
|
|
4628
|
-
}, "恢复默认")));
|
|
4630
|
+
}, "恢复默认"))));
|
|
4629
4631
|
}
|
|
4630
4632
|
//#endregion
|
|
4631
4633
|
//#region src/client/ui/LspSettings.ts
|
|
@@ -4862,8 +4864,10 @@ window.__ModuleLoader__.load({
|
|
|
4862
4864
|
onClick: refresh
|
|
4863
4865
|
}, "↻ 刷新"));
|
|
4864
4866
|
const banner = error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null;
|
|
4867
|
+
/** 线框面板容器:标题 + 内容体。 */
|
|
4868
|
+
const panel = (title, children) => react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, title), react.default.createElement("div", { className: "vsm-panel-body" }, children));
|
|
4865
4869
|
let body = null;
|
|
4866
|
-
if (tab === "servers") body = react.default.createElement(
|
|
4870
|
+
if (tab === "servers") body = panel("语言服务器", react.default.createElement(react.default.Fragment, null, LANGUAGES.map((lang) => react.default.createElement(LangCard, {
|
|
4867
4871
|
key: lang.id,
|
|
4868
4872
|
lang,
|
|
4869
4873
|
config: config[lang.id] ?? {},
|
|
@@ -4871,15 +4875,15 @@ window.__ModuleLoader__.load({
|
|
|
4871
4875
|
busy,
|
|
4872
4876
|
onToggle: toggleLang,
|
|
4873
4877
|
onSave: saveLang
|
|
4874
|
-
})), react.default.createElement("div", { className: "vsm-lsp-hint" }, "提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。"));
|
|
4875
|
-
else if (tab === "installed") body = react.default.createElement(
|
|
4878
|
+
})), react.default.createElement("div", { className: "vsm-lsp-hint" }, "提示:打开 Lua/C# 文件即自动(惰性)启动对应语言服务器;未配置时编辑器功能不受影响,大纲回退内置解析。")));
|
|
4879
|
+
else if (tab === "installed") body = panel("已安装扩展", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已安装的语言服务器扩展(存于 ~/.dsh/dsh-vscode-mode/extensions/)。语言服务器被自动发现为「扩展」源。"), installed.length ? installed.map((ext) => react.default.createElement(InstalledCard, {
|
|
4876
4880
|
key: ext.id,
|
|
4877
4881
|
ext,
|
|
4878
4882
|
busy,
|
|
4879
4883
|
onUninstall: uninstallExt,
|
|
4880
4884
|
onUpdate: updateExt
|
|
4881
|
-
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有安装扩展(去「市场」或指定本地 .vsix)"));
|
|
4882
|
-
else if (tab === "market") body = react.default.createElement(
|
|
4885
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有安装扩展(去「市场」或指定本地 .vsix)")));
|
|
4886
|
+
else if (tab === "market") body = panel("扩展市场", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-form" }, react.default.createElement("label", null, react.default.createElement("span", null, "搜索 Open VSX 市场"), react.default.createElement("input", {
|
|
4883
4887
|
value: query,
|
|
4884
4888
|
placeholder: "如 sumneko.lua / lua language server",
|
|
4885
4889
|
onChange: (e) => setQuery(e.target.value),
|
|
@@ -4904,18 +4908,107 @@ window.__ModuleLoader__.load({
|
|
|
4904
4908
|
installed,
|
|
4905
4909
|
busy,
|
|
4906
4910
|
onInstall: installMarket
|
|
4907
|
-
})));
|
|
4908
|
-
else body = react.default.createElement(
|
|
4911
|
+
}))));
|
|
4912
|
+
else body = panel("可用更新", react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-lsp-hint" }, "已装扩展与 Open VSX 最新版的差异。"), updates.length ? updates.map((u) => react.default.createElement("article", {
|
|
4909
4913
|
key: u.id,
|
|
4910
4914
|
className: "vsm-mcp-card"
|
|
4911
4915
|
}, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot error" }), react.default.createElement("span", null, u.displayName || u.id), react.default.createElement("span", { className: "vsm-ext-ver" }, "v" + u.current + " → v" + u.latest)), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
|
|
4912
4916
|
className: "vsm-primary vsm-small",
|
|
4913
4917
|
disabled: busy === u.id,
|
|
4914
4918
|
onClick: () => updateExt(u)
|
|
4915
|
-
}, busy === u.id ? "更新中…" : "更新"))))) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "全部已是最新"));
|
|
4919
|
+
}, busy === u.id ? "更新中…" : "更新"))))) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "全部已是最新")));
|
|
4916
4920
|
return react.default.createElement("div", null, header, nav, banner, body);
|
|
4917
4921
|
}
|
|
4918
4922
|
//#endregion
|
|
4923
|
+
//#region src/client/state/workspaceFoldCache.ts
|
|
4924
|
+
/**
|
|
4925
|
+
* dsh-vscode-mode client — 「性能优化」页工作区栏目展开状态持久化(纯函数,可单测)。
|
|
4926
|
+
* 只持久化「已展开」的工作区键集合(默认全部折叠),对齐 explorerCache 的做法:
|
|
4927
|
+
* 不缓存会话条目本身,条目由 edrv.perf.inventory 实时返回。
|
|
4928
|
+
* 键前缀统一走 paths.ts PathConst(CACHE_KEY.workspaceFold)。
|
|
4929
|
+
* 作者 ddj 2026-09-02
|
|
4930
|
+
*/
|
|
4931
|
+
/** localStorage 键(全局,不按会话隔离)。 */
|
|
4932
|
+
const STORE_KEY = CACHE_KEY.workspaceFold;
|
|
4933
|
+
/**
|
|
4934
|
+
* 归一化工作区键:仅去首尾空白(工作区键非文件系统路径,不做斜杠转换)。
|
|
4935
|
+
* @author ddj 2026年09月02号
|
|
4936
|
+
* @param key 工作区标识
|
|
4937
|
+
* @returns 归一化键,空值返回 ''
|
|
4938
|
+
*/
|
|
4939
|
+
function normalizeFoldKey(key) {
|
|
4940
|
+
return String(key ?? "").trim();
|
|
4941
|
+
}
|
|
4942
|
+
/**
|
|
4943
|
+
* 裁剪展开键集:去重、丢弃空值、超容量丢弃尾部(保持传入顺序)。
|
|
4944
|
+
* @author ddj 2026年09月02号
|
|
4945
|
+
* @param expanded 原始展开键集合
|
|
4946
|
+
* @returns 规范化后的键数组
|
|
4947
|
+
*/
|
|
4948
|
+
function sanitizeFolded(expanded) {
|
|
4949
|
+
if (!Array.isArray(expanded)) return [];
|
|
4950
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4951
|
+
const out = [];
|
|
4952
|
+
for (const item of expanded) {
|
|
4953
|
+
const key = normalizeFoldKey(typeof item === "string" ? item : "");
|
|
4954
|
+
if (!key || seen.has(key)) continue;
|
|
4955
|
+
seen.add(key);
|
|
4956
|
+
out.push(key);
|
|
4957
|
+
if (out.length >= 300) break;
|
|
4958
|
+
}
|
|
4959
|
+
return out;
|
|
4960
|
+
}
|
|
4961
|
+
/**
|
|
4962
|
+
* 序列化展开状态(JSON,带 schema 版本号)。
|
|
4963
|
+
* @author ddj 2026年09月02号
|
|
4964
|
+
* @param expanded 已展开的项目键集合
|
|
4965
|
+
* @returns JSON 字符串
|
|
4966
|
+
*/
|
|
4967
|
+
function serializeFold(expanded) {
|
|
4968
|
+
return JSON.stringify({
|
|
4969
|
+
v: 1,
|
|
4970
|
+
expanded: sanitizeFolded(expanded)
|
|
4971
|
+
});
|
|
4972
|
+
}
|
|
4973
|
+
/**
|
|
4974
|
+
* 解析缓存文本;损坏/格式不符 → 空集合。
|
|
4975
|
+
* @author ddj 2026年09月02号
|
|
4976
|
+
* @param text localStorage 原文
|
|
4977
|
+
* @returns 已展开键集合
|
|
4978
|
+
*/
|
|
4979
|
+
function parseFold(text) {
|
|
4980
|
+
if (!text) return [];
|
|
4981
|
+
try {
|
|
4982
|
+
const data = JSON.parse(text);
|
|
4983
|
+
if (!data || typeof data !== "object") return [];
|
|
4984
|
+
return sanitizeFolded(data.expanded);
|
|
4985
|
+
} catch (error) {
|
|
4986
|
+
return [];
|
|
4987
|
+
}
|
|
4988
|
+
}
|
|
4989
|
+
/**
|
|
4990
|
+
* 读取展开状态(localStorage 不可用 → 空集合)。
|
|
4991
|
+
* @author ddj 2026年09月02号
|
|
4992
|
+
* @returns 已展开键集合
|
|
4993
|
+
*/
|
|
4994
|
+
function foldLoad() {
|
|
4995
|
+
try {
|
|
4996
|
+
return parseFold(window.localStorage.getItem(STORE_KEY));
|
|
4997
|
+
} catch (error) {
|
|
4998
|
+
return [];
|
|
4999
|
+
}
|
|
5000
|
+
}
|
|
5001
|
+
/**
|
|
5002
|
+
* 写入展开状态(配额满/隐私模式失败静默)。
|
|
5003
|
+
* @author ddj 2026年09月02号
|
|
5004
|
+
* @param expanded 已展开键集合
|
|
5005
|
+
*/
|
|
5006
|
+
function foldSave(expanded) {
|
|
5007
|
+
try {
|
|
5008
|
+
window.localStorage.setItem(STORE_KEY, serializeFold(expanded));
|
|
5009
|
+
} catch (error) {}
|
|
5010
|
+
}
|
|
5011
|
+
//#endregion
|
|
4919
5012
|
//#region src/client/ui/PerfSettings.ts
|
|
4920
5013
|
/**
|
|
4921
5014
|
* dsh-vscode-mode client — PerfSettings:「性能优化」设置子页(会话卫生 + 压缩调优)。
|
|
@@ -4935,6 +5028,43 @@ window.__ModuleLoader__.load({
|
|
|
4935
5028
|
}
|
|
4936
5029
|
/** 会话行 key:workspaceKey \u0000 sessionId。 */
|
|
4937
5030
|
const keyOf = (ws, id) => ws + "\0" + id;
|
|
5031
|
+
/** 工作区列表每页条数。 */
|
|
5032
|
+
const PAGE_SIZE = 7;
|
|
5033
|
+
/**
|
|
5034
|
+
* 计算分页(纯函数,可单测)。
|
|
5035
|
+
* 作者 ddj 2026年09月02号
|
|
5036
|
+
* @param total 总条数
|
|
5037
|
+
* @param pageSize 每页条数
|
|
5038
|
+
* @param page 当前页码(1 起)
|
|
5039
|
+
* @returns 归一化后的分页信息(page 被夹到有效范围)
|
|
5040
|
+
*/
|
|
5041
|
+
function paginate(total, pageSize, page) {
|
|
5042
|
+
const size = Math.max(1, Math.floor(pageSize) || 1);
|
|
5043
|
+
const pages = Math.max(1, Math.ceil(total / size));
|
|
5044
|
+
const current = Math.min(Math.max(1, Math.floor(page) || 1), pages);
|
|
5045
|
+
const start = (current - 1) * size;
|
|
5046
|
+
return {
|
|
5047
|
+
page: current,
|
|
5048
|
+
pages,
|
|
5049
|
+
pageSize: size,
|
|
5050
|
+
start,
|
|
5051
|
+
end: Math.min(start + size, total),
|
|
5052
|
+
total
|
|
5053
|
+
};
|
|
5054
|
+
}
|
|
5055
|
+
/**
|
|
5056
|
+
* 取当前页切片(纯函数,可单测)。
|
|
5057
|
+
* 作者 ddj 2026年09月02号
|
|
5058
|
+
* @param items 全量列表
|
|
5059
|
+
* @param page 当前页码(1 起)
|
|
5060
|
+
* @param pageSize 每页条数
|
|
5061
|
+
* @returns 当前页子数组
|
|
5062
|
+
*/
|
|
5063
|
+
function pageSlice(items, page, pageSize) {
|
|
5064
|
+
const list = items ?? [];
|
|
5065
|
+
const { start, end } = paginate(list.length, pageSize, page);
|
|
5066
|
+
return list.slice(start, end);
|
|
5067
|
+
}
|
|
4938
5068
|
/** 单行会话。 */
|
|
4939
5069
|
function SessionRow({ row, checked, disabled, onToggle }) {
|
|
4940
5070
|
const age = Math.max(0, (Date.now() - row.mtime) / 864e5);
|
|
@@ -4953,6 +5083,63 @@ window.__ModuleLoader__.load({
|
|
|
4953
5083
|
}, when), row.active ? react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-active" }, "活跃") : null);
|
|
4954
5084
|
}
|
|
4955
5085
|
/**
|
|
5086
|
+
* 工作区列表分页器:顶部文案 + 上一页/下一页交互。
|
|
5087
|
+
* 作者 ddj 2026年09月02号
|
|
5088
|
+
* @param {object} info paginate() 的分页信息
|
|
5089
|
+
* @param {Function} onPrev 上一页回调
|
|
5090
|
+
* @param {Function} onNext 下一页回调
|
|
5091
|
+
*/
|
|
5092
|
+
function WorkspacePager({ info, onPrev, onNext }) {
|
|
5093
|
+
const { page, pages, start, end, total } = info;
|
|
5094
|
+
return react.default.createElement("div", { className: "vsm-perf-pager" }, react.default.createElement("span", { className: "vsm-perf-pager-text" }, total ? `第 ${start + 1}–${end} 个 / 共 ${total} 个工作区(第 ${page}/${pages} 页)` : "暂无工作区"), react.default.createElement("div", { className: "vsm-perf-pager-actions" }, react.default.createElement("button", {
|
|
5095
|
+
className: "vsm-secondary vsm-small",
|
|
5096
|
+
onClick: onPrev,
|
|
5097
|
+
disabled: page <= 1
|
|
5098
|
+
}, "‹ 上一页"), react.default.createElement("button", {
|
|
5099
|
+
className: "vsm-secondary vsm-small",
|
|
5100
|
+
onClick: onNext,
|
|
5101
|
+
disabled: page >= pages
|
|
5102
|
+
}, "下一页 ›")));
|
|
5103
|
+
}
|
|
5104
|
+
/**
|
|
5105
|
+
* 工作区栏目(可展开/折叠),内含该工作区的会话行。
|
|
5106
|
+
* 作者 ddj 2026年09月02号
|
|
5107
|
+
* @param {object} ws 工作区汇总(workspaceKey / totalBytes)
|
|
5108
|
+
* @param {object[]} rows 该工作区的会话行
|
|
5109
|
+
* @param {boolean} expanded 是否展开
|
|
5110
|
+
* @param {Function} onToggleFold 展开/折叠切换回调
|
|
5111
|
+
* @param {object} selected 已选会话键集合
|
|
5112
|
+
* @param {boolean} locked 会话勾选是否禁用(移出执行中)
|
|
5113
|
+
* @param {Function} onToggleRow 单行勾选回调
|
|
5114
|
+
*/
|
|
5115
|
+
function WorkspaceBlock({ ws, rows, expanded, onToggleFold, selected, locked, onToggleRow }) {
|
|
5116
|
+
const head = react.default.createElement("div", {
|
|
5117
|
+
className: "vsm-perf-ws-head",
|
|
5118
|
+
role: "button",
|
|
5119
|
+
tabIndex: 0,
|
|
5120
|
+
"aria-expanded": expanded,
|
|
5121
|
+
onClick: onToggleFold,
|
|
5122
|
+
onKeyDown: (event) => {
|
|
5123
|
+
if (event.key === "Enter" || event.key === " ") {
|
|
5124
|
+
event.preventDefault();
|
|
5125
|
+
onToggleFold();
|
|
5126
|
+
}
|
|
5127
|
+
}
|
|
5128
|
+
}, react.default.createElement("span", { className: "vsm-perf-ws-caret " + (expanded ? "open" : "") }, "▸"), react.default.createElement("span", {
|
|
5129
|
+
className: "vsm-perf-ws-name",
|
|
5130
|
+
title: ws.workspaceKey
|
|
5131
|
+
}, ws.workspaceKey), react.default.createElement("span", { className: "vsm-perf-ws-meta" }, rows.length + " 会话 · " + formatBytes(ws.totalBytes)));
|
|
5132
|
+
if (!expanded) return react.default.createElement("div", { className: "vsm-perf-ws" }, head);
|
|
5133
|
+
const body = rows.length ? rows.map((row) => react.default.createElement(SessionRow, {
|
|
5134
|
+
key: keyOf(row.workspaceKey, row.sessionId),
|
|
5135
|
+
row,
|
|
5136
|
+
checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
|
|
5137
|
+
disabled: row.active || locked,
|
|
5138
|
+
onToggle: () => onToggleRow(keyOf(row.workspaceKey, row.sessionId))
|
|
5139
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话");
|
|
5140
|
+
return react.default.createElement("div", { className: "vsm-perf-ws expanded" }, head, react.default.createElement("div", { className: "vsm-perf-rows" }, body));
|
|
5141
|
+
}
|
|
5142
|
+
/**
|
|
4956
5143
|
* 会话卫生 + 压缩调优设置子页。
|
|
4957
5144
|
* @author ddj 2026年09月02号
|
|
4958
5145
|
*/
|
|
@@ -4964,6 +5151,26 @@ window.__ModuleLoader__.load({
|
|
|
4964
5151
|
const [confirm, setConfirm] = react.default.useState(null);
|
|
4965
5152
|
const [moved, setMoved] = react.default.useState(null);
|
|
4966
5153
|
const [config, setConfig] = react.default.useState(null);
|
|
5154
|
+
const [expanded, setExpanded] = react.default.useState(() => foldLoad());
|
|
5155
|
+
const [page, setPage] = react.default.useState(1);
|
|
5156
|
+
react.default.useEffect(() => {
|
|
5157
|
+
foldSave(expanded);
|
|
5158
|
+
}, [expanded]);
|
|
5159
|
+
/** 数据变化后把越界页码夹回有效范围。 */
|
|
5160
|
+
const workspaces = inv?.workspaces ?? [];
|
|
5161
|
+
const pageInfo = paginate(workspaces.length, PAGE_SIZE, page);
|
|
5162
|
+
react.default.useEffect(() => {
|
|
5163
|
+
if (page !== pageInfo.page) setPage(pageInfo.page);
|
|
5164
|
+
}, [page, pageInfo.page]);
|
|
5165
|
+
/** 切换某工作区栏目的展开/折叠(默认全折叠)。 */
|
|
5166
|
+
const toggleFold = (key) => setExpanded((old) => old.includes(key) ? old.filter((item) => item !== key) : old.concat(key));
|
|
5167
|
+
/** 切换单会话勾选。 */
|
|
5168
|
+
const toggleRow = (k) => setSelected((old) => {
|
|
5169
|
+
const next = { ...old };
|
|
5170
|
+
if (next[k]) delete next[k];
|
|
5171
|
+
else next[k] = true;
|
|
5172
|
+
return next;
|
|
5173
|
+
});
|
|
4967
5174
|
const load = react.default.useCallback(() => {
|
|
4968
5175
|
setError("");
|
|
4969
5176
|
Promise.all([rpc("edrv.perf.inventory", {}), rpc("edrv.perf.configGet", {})]).then(([i, c]) => {
|
|
@@ -5066,30 +5273,25 @@ window.__ModuleLoader__.load({
|
|
|
5066
5273
|
};
|
|
5067
5274
|
const totals = inv?.totals;
|
|
5068
5275
|
const summaryCards = totals ? react.default.createElement("div", { className: "vsm-perf-cards" }, react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "工作区"), react.default.createElement("b", null, String(totals.workspaces))), react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "会话数"), react.default.createElement("b", null, String(totals.sessions))), react.default.createElement("div", { className: "vsm-perf-card" }, react.default.createElement("span", null, "压缩态总体积"), react.default.createElement("b", { className: totals.totalBytes >= 52428800 ? "big" : "" }, formatBytes(totals.totalBytes)))) : null;
|
|
5069
|
-
const
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
}, react.default.createElement("div", { className: "vsm-perf-ws-head" }, react.default.createElement("span", {
|
|
5075
|
-
className: "vsm-perf-ws-name",
|
|
5076
|
-
title: ws.workspaceKey
|
|
5077
|
-
}, ws.workspaceKey), react.default.createElement("span", { className: "vsm-perf-ws-meta" }, rows.length + " 会话 · " + formatBytes(ws.totalBytes))), react.default.createElement("div", { className: "vsm-perf-rows" }, rows.length ? rows.map((row) => react.default.createElement(SessionRow, {
|
|
5078
|
-
key: keyOf(row.workspaceKey, row.sessionId),
|
|
5079
|
-
row,
|
|
5080
|
-
checked: !!selected[keyOf(row.workspaceKey, row.sessionId)],
|
|
5081
|
-
disabled: row.active || busy === "move",
|
|
5082
|
-
onToggle: () => {
|
|
5083
|
-
const k = keyOf(row.workspaceKey, row.sessionId);
|
|
5084
|
-
setSelected((old) => {
|
|
5085
|
-
const next = { ...old };
|
|
5086
|
-
if (next[k]) delete next[k];
|
|
5087
|
-
else next[k] = true;
|
|
5088
|
-
return next;
|
|
5089
|
-
});
|
|
5090
|
-
}
|
|
5091
|
-
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "无会话")));
|
|
5276
|
+
const visible = pageSlice(workspaces, pageInfo.page, PAGE_SIZE);
|
|
5277
|
+
const workspacePager = workspaces.length > PAGE_SIZE ? react.default.createElement(WorkspacePager, {
|
|
5278
|
+
info: pageInfo,
|
|
5279
|
+
onPrev: () => setPage((old) => Math.max(1, old - 1)),
|
|
5280
|
+
onNext: () => setPage((old) => Math.min(pageInfo.pages, old + 1))
|
|
5092
5281
|
}) : null;
|
|
5282
|
+
const workspaceBlocks = workspaces.length ? react.default.createElement("div", { className: "vsm-perf-ws-list" }, visible.map((ws) => {
|
|
5283
|
+
const key = normalizeFoldKey(ws.workspaceKey);
|
|
5284
|
+
return react.default.createElement(WorkspaceBlock, {
|
|
5285
|
+
key: ws.workspaceKey,
|
|
5286
|
+
ws,
|
|
5287
|
+
rows: inv.sessions.filter((s) => s.workspaceKey === ws.workspaceKey),
|
|
5288
|
+
expanded: expanded.includes(key),
|
|
5289
|
+
onToggleFold: () => toggleFold(key),
|
|
5290
|
+
selected,
|
|
5291
|
+
locked: busy === "move",
|
|
5292
|
+
onToggleRow: toggleRow
|
|
5293
|
+
});
|
|
5294
|
+
})) : null;
|
|
5093
5295
|
const selCount = Object.keys(selected).length;
|
|
5094
5296
|
const confirmBody = confirm ? react.default.createElement("div", { className: "vsm-perf-modal" }, react.default.createElement("div", { className: "vsm-perf-modal-card" }, react.default.createElement("h3", null, "确认移出 " + confirm.items.length + " 个会话?"), react.default.createElement("p", { className: "vsm-perf-modal-note" }, "将从 ~/.dsh/sessions 移出到 ~/.dsh/sessions-archive(可逆,重启后不再回放,释放 " + formatBytes(confirm.reclaimedBytes) + ")。活跃会话已排除。"), react.default.createElement("ul", { className: "vsm-perf-modal-list" }, confirm.items.slice(0, 20).map((item) => react.default.createElement("li", { key: item.workspaceKey + item.sessionId }, item.workspaceKey + " / " + item.sessionId + "(" + formatBytes(item.bytes) + ")"))), confirm.items.length > 20 ? react.default.createElement("div", { className: "vsm-perf-modal-more" }, "…等 " + confirm.items.length + " 个") : null, react.default.createElement("div", { className: "vsm-perf-modal-actions" }, react.default.createElement("button", {
|
|
5095
5297
|
className: "vsm-primary",
|
|
@@ -5099,15 +5301,15 @@ window.__ModuleLoader__.load({
|
|
|
5099
5301
|
className: "vsm-secondary",
|
|
5100
5302
|
onClick: () => setConfirm(null)
|
|
5101
5303
|
}, "取消")))) : null;
|
|
5102
|
-
const movedBody = moved?.moved?.length ? react.default.createElement("
|
|
5304
|
+
const movedBody = moved?.moved?.length ? react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "已移出 " + moved.moved.length + " 个会话(释放 " + formatBytes(moved.reclaimedBytes) + ")"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-perf-rows" }, moved.moved.map((item) => react.default.createElement("div", {
|
|
5103
5305
|
key: item.workspaceKey + item.sessionId,
|
|
5104
5306
|
className: "vsm-perf-row"
|
|
5105
5307
|
}, react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-id" }, item.sessionId), react.default.createElement("span", { className: "vsm-perf-cell vsm-perf-size" }, formatBytes(item.bytes)), react.default.createElement("button", {
|
|
5106
5308
|
className: "vsm-secondary vsm-small",
|
|
5107
5309
|
onClick: () => doRestore(item),
|
|
5108
5310
|
disabled: busy === "restore"
|
|
5109
|
-
}, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null) : null;
|
|
5110
|
-
const configBody = config ? react.default.createElement("
|
|
5311
|
+
}, "恢复")))), moved.failures?.length ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "失败:" + moved.failures.join(";")) : null)) : null;
|
|
5312
|
+
const configBody = config ? react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "DSH 内置压缩调优"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", null, "调低 compaction-basic / tool-result-pruner 触发阈值,让长循环会话更早压缩,降低模型可见上下文压力(减少 token 与防 context overflow)。"), react.default.createElement("p", { className: "vsm-perf-note" }, "注意:压缩只追加 compaction 事件,不重写已持久化会话日志(append-only),因此不会缩小已存在的会话文件——存量瘦身请用上方「移出到归档」。packChunks 已默认开启,无需配置。"), react.default.createElement("div", { className: "vsm-perf-config-state" }, react.default.createElement("span", { className: "vsm-perf-cell" }, "状态:" + (config.applied ? "已应用" : "未应用")), config.applied ? react.default.createElement("button", {
|
|
5111
5313
|
className: "vsm-secondary vsm-small",
|
|
5112
5314
|
onClick: undoConfig,
|
|
5113
5315
|
disabled: busy === "undo"
|
|
@@ -5115,9 +5317,9 @@ window.__ModuleLoader__.load({
|
|
|
5115
5317
|
className: "vsm-primary vsm-small",
|
|
5116
5318
|
onClick: applyConfig,
|
|
5117
5319
|
disabled: busy === "apply"
|
|
5118
|
-
}, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block)) : null;
|
|
5119
|
-
const guideBody = react.default.createElement("
|
|
5120
|
-
return react.default.createElement("section", { className: "vsm-perf-page" }, react.default.createElement("h2", null, "会话性能"), react.default.createElement("p", null, "DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。"), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, summaryCards, react.default.createElement("div", { className: "vsm-perf-toolbar" }, react.default.createElement("button", {
|
|
5320
|
+
}, "应用建议配置(需重启)")), react.default.createElement("pre", { className: "vsm-perf-block" }, config.block))) : null;
|
|
5321
|
+
const guideBody = react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "侧车摘要(agent/脚本取关键字段,不整份读 sidecar)"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("p", null, "工作区根目录的 .dsh-edit-review.json(可能数 MB)只应通过摘要接口读取,禁止 agent 整份 read/write 进对话:"), react.default.createElement("pre", { className: "vsm-perf-block" }, "curl -s -X POST http://127.0.0.1:3080/edrv/rpc -H 'content-type: application/json' -d '{\"method\":\"edrv.perf.sidecarSummary\",\"args\":{}}'\n# 差异记录列表(含每文件 pending 摘要):edrv.list\n# 历史归档:edrv.archiveList / edrv.archiveRead")));
|
|
5322
|
+
return react.default.createElement("section", { className: "vsm-perf-page" }, react.default.createElement("h2", null, "会话性能"), react.default.createElement("p", null, "DSH 启动会回放 ~/.dsh/sessions 全部会话(V8 展开约 10×),体积越大内存越吃紧。定期把巨型/旧会话移出到归档可显著降低启动内存。"), error ? react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error) : null, summaryCards, react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "会话盘点"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("div", { className: "vsm-perf-toolbar" }, react.default.createElement("button", {
|
|
5121
5323
|
className: "vsm-secondary vsm-small",
|
|
5122
5324
|
onClick: selectBig
|
|
5123
5325
|
}, "圈选 ≥1MB 非活跃"), react.default.createElement("button", {
|
|
@@ -5136,15 +5338,14 @@ window.__ModuleLoader__.load({
|
|
|
5136
5338
|
className: "vsm-secondary vsm-small",
|
|
5137
5339
|
onClick: load,
|
|
5138
5340
|
disabled: busy === "load"
|
|
5139
|
-
}, "⟳ 刷新")), workspaceBlocks, movedBody, configBody, guideBody, confirmBody);
|
|
5341
|
+
}, "⟳ 刷新")), workspacePager, workspaceBlocks)), movedBody, configBody, guideBody, confirmBody);
|
|
5140
5342
|
}
|
|
5141
5343
|
//#endregion
|
|
5142
5344
|
//#region src/client/ui/McpSettings.ts
|
|
5143
5345
|
/**
|
|
5144
|
-
* dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP
|
|
5145
|
-
*
|
|
5146
|
-
*
|
|
5147
|
-
* 作者 ddj 2026年08月22号 / 2026年08月26号
|
|
5346
|
+
* dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
|
|
5347
|
+
* MCP 管理 Tab 下含三个子页签:我的 MCP(profile 全局)/ 项目 MCP(各项目 .mcp.json)/ MCP 市场(占位)。
|
|
5348
|
+
* 作者 ddj 2026年08月22号 / 2026年08月26号 / 2026年08月27号
|
|
5148
5349
|
*/
|
|
5149
5350
|
const EMPTY = {
|
|
5150
5351
|
serverName: "",
|
|
@@ -5357,14 +5558,14 @@ window.__ModuleLoader__.load({
|
|
|
5357
5558
|
disabled: devBusy,
|
|
5358
5559
|
onClick: closeDevForm
|
|
5359
5560
|
}, devBusy ? "切换中…" : "关闭开发形态"))) : null;
|
|
5360
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
|
|
5561
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, "文件与安装"), react.default.createElement("div", { className: "vsm-panel-body" }, react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
|
|
5361
5562
|
value: currentOption,
|
|
5362
5563
|
disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
|
|
5363
5564
|
onChange: (event) => save(event.target.value)
|
|
5364
5565
|
}, options.map((item) => react.default.createElement("option", {
|
|
5365
5566
|
key: item.id,
|
|
5366
5567
|
value: item.id
|
|
5367
|
-
}, item.label))), loading && react.default.createElement("small", null, "读取中…")), devFormRow);
|
|
5568
|
+
}, item.label))), loading && react.default.createElement("small", null, "读取中…")), devFormRow)));
|
|
5368
5569
|
}
|
|
5369
5570
|
/** 兼容性小节:host 报告(RPC compat)+ 客户端本地适配项。 */
|
|
5370
5571
|
function CompatSection({ getSummary }) {
|
|
@@ -5387,12 +5588,111 @@ window.__ModuleLoader__.load({
|
|
|
5387
5588
|
key: item.name,
|
|
5388
5589
|
className: "vsm-compat-item " + (item.active ? "ok" : "warn")
|
|
5389
5590
|
}, react.default.createElement("span", { className: "vsm-compat-name" }, item.name), react.default.createElement("span", { className: "vsm-compat-note" }, item.note ?? (item.active ? "正常" : "未生效"))));
|
|
5591
|
+
/** 兼容性分组面板:标题 + 线框内容区。 */
|
|
5592
|
+
const group = (title, children) => react.default.createElement("section", { className: "vsm-panel" }, react.default.createElement("h3", { className: "vsm-panel-title" }, title), react.default.createElement("div", { className: "vsm-panel-body" }, children));
|
|
5390
5593
|
const warnings = report?.warnings ?? [];
|
|
5391
5594
|
const devForm = report?.devForm;
|
|
5392
|
-
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled &&
|
|
5595
|
+
return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), devForm?.enabled && group("开发形态", react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "开发形态开启(工作区链接安装)"), react.default.createElement("span", { className: "vsm-compat-note" }, devForm.path))), group("外部插件与服务", renderItems(report?.external)), group("客户端适配", renderItems(getSummary?.() ?? [])), group("护栏", renderItems(report?.guards)), group("警告", warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
|
|
5393
5596
|
key: index,
|
|
5394
5597
|
className: "vsm-mcp-error vsm-mcp-banner"
|
|
5395
|
-
}, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, "")));
|
|
5598
|
+
}, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, ""))));
|
|
5599
|
+
}
|
|
5600
|
+
/** MCP 管理子页签定义(我的 / 项目 / 市场)。 */
|
|
5601
|
+
const MCP_TABS = [
|
|
5602
|
+
{
|
|
5603
|
+
id: "mine",
|
|
5604
|
+
label: "我的 MCP"
|
|
5605
|
+
},
|
|
5606
|
+
{
|
|
5607
|
+
id: "projects",
|
|
5608
|
+
label: "项目 MCP"
|
|
5609
|
+
},
|
|
5610
|
+
{
|
|
5611
|
+
id: "market",
|
|
5612
|
+
label: "MCP 市场"
|
|
5613
|
+
}
|
|
5614
|
+
];
|
|
5615
|
+
/**
|
|
5616
|
+
* MCP 管理面板:顶层「MCP 管理」Tab 下的三个子页签容器。
|
|
5617
|
+
* 作者 ddj 2026年08月27号
|
|
5618
|
+
* @param {object[]} servers 全局 MCP 服务列表
|
|
5619
|
+
* @param {object[]} projects 项目及其项目级 MCP 列表
|
|
5620
|
+
* @param {string} busy 正在执行的操作标识
|
|
5621
|
+
* @param {object} draft 新增表单草稿
|
|
5622
|
+
* @param {Function} edit 草稿字段变更回调
|
|
5623
|
+
* @param {Function} resetDraft 清空草稿回调
|
|
5624
|
+
*/
|
|
5625
|
+
function McpManagePanel({ servers, projects, busy, draft, edit, resetDraft, refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject, saveGlobal, saveProject }) {
|
|
5626
|
+
const [mcpTab, setMcpTab] = react.default.useState("mine");
|
|
5627
|
+
const [showForm, setShowForm] = react.default.useState(false);
|
|
5628
|
+
const [projectForm, setProjectForm] = react.default.useState(null);
|
|
5629
|
+
const [selectedPath, setSelectedPath] = react.default.useState("");
|
|
5630
|
+
react.default.useEffect(() => {
|
|
5631
|
+
if (!projects.length) {
|
|
5632
|
+
setSelectedPath("");
|
|
5633
|
+
return;
|
|
5634
|
+
}
|
|
5635
|
+
if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
|
|
5636
|
+
}, [projects, selectedPath]);
|
|
5637
|
+
const closeGlobalForm = () => setShowForm(false);
|
|
5638
|
+
const closeProjectForm = () => setProjectForm(null);
|
|
5639
|
+
const addProject = (project) => {
|
|
5640
|
+
resetDraft?.();
|
|
5641
|
+
setProjectForm({
|
|
5642
|
+
workspacePath: project.workspacePath,
|
|
5643
|
+
title: project.title
|
|
5644
|
+
});
|
|
5645
|
+
};
|
|
5646
|
+
const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
|
|
5647
|
+
const mineBody = servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
|
|
5648
|
+
key: s.id,
|
|
5649
|
+
server: s,
|
|
5650
|
+
onRefresh: refreshGlobal,
|
|
5651
|
+
onToggle: toggleGlobal,
|
|
5652
|
+
onRemove: removeGlobal
|
|
5653
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP");
|
|
5654
|
+
const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
|
|
5655
|
+
projects,
|
|
5656
|
+
value: selectedPath,
|
|
5657
|
+
onChange: setSelectedPath
|
|
5658
|
+
}), selectedProject ? react.default.createElement(ProjectGroup, {
|
|
5659
|
+
project: selectedProject,
|
|
5660
|
+
busy,
|
|
5661
|
+
onAdd: addProject,
|
|
5662
|
+
onRefresh: refreshProject,
|
|
5663
|
+
onToggle: toggleProject,
|
|
5664
|
+
onRemove: removeProject
|
|
5665
|
+
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
|
|
5666
|
+
let body;
|
|
5667
|
+
if (mcpTab === "projects") body = projectBody;
|
|
5668
|
+
else if (mcpTab === "market") body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "MCP 市场暂未接入");
|
|
5669
|
+
else body = mineBody;
|
|
5670
|
+
const form = showForm ? react.default.createElement(McpForm, {
|
|
5671
|
+
title: "添加全局 MCP",
|
|
5672
|
+
draft,
|
|
5673
|
+
busy,
|
|
5674
|
+
edit,
|
|
5675
|
+
save: () => saveGlobal(closeGlobalForm),
|
|
5676
|
+
close: closeGlobalForm
|
|
5677
|
+
}) : projectForm ? react.default.createElement(McpForm, {
|
|
5678
|
+
title: "添加项目 MCP · " + projectForm.title,
|
|
5679
|
+
draft,
|
|
5680
|
+
busy,
|
|
5681
|
+
edit,
|
|
5682
|
+
save: () => saveProject(projectForm.workspacePath, closeProjectForm),
|
|
5683
|
+
close: closeProjectForm
|
|
5684
|
+
}) : null;
|
|
5685
|
+
return react.default.createElement(react.default.Fragment, null, react.default.createElement("div", { className: "vsm-mcp-subhead" }, react.default.createElement("nav", { className: "vsm-mcp-subtabs" }, MCP_TABS.map((item) => react.default.createElement("button", {
|
|
5686
|
+
key: item.id,
|
|
5687
|
+
className: mcpTab === item.id ? "active" : "",
|
|
5688
|
+
onClick: () => setMcpTab(item.id)
|
|
5689
|
+
}, item.label))), react.default.createElement("button", {
|
|
5690
|
+
className: "vsm-primary vsm-small",
|
|
5691
|
+
onClick: () => {
|
|
5692
|
+
resetDraft?.();
|
|
5693
|
+
setShowForm(true);
|
|
5694
|
+
}
|
|
5695
|
+
}, "+ 添加全局 MCP")), body, form);
|
|
5396
5696
|
}
|
|
5397
5697
|
/** MCP 管理主面板。 */
|
|
5398
5698
|
function McpSettings(props) {
|
|
@@ -5405,16 +5705,6 @@ window.__ModuleLoader__.load({
|
|
|
5405
5705
|
const [error, setError] = react.default.useState("");
|
|
5406
5706
|
const [tab, setTab] = react.default.useState("general");
|
|
5407
5707
|
const [draft, setDraft] = react.default.useState(EMPTY);
|
|
5408
|
-
const [showForm, setShowForm] = react.default.useState(false);
|
|
5409
|
-
const [projectForm, setProjectForm] = react.default.useState(null);
|
|
5410
|
-
const [selectedPath, setSelectedPath] = react.default.useState("");
|
|
5411
|
-
react.default.useEffect(() => {
|
|
5412
|
-
if (!projects.length) {
|
|
5413
|
-
setSelectedPath("");
|
|
5414
|
-
return;
|
|
5415
|
-
}
|
|
5416
|
-
if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
|
|
5417
|
-
}, [projects, selectedPath]);
|
|
5418
5708
|
const loadAll = react.default.useCallback(() => {
|
|
5419
5709
|
setLoading(true);
|
|
5420
5710
|
setError("");
|
|
@@ -5464,116 +5754,87 @@ window.__ModuleLoader__.load({
|
|
|
5464
5754
|
workspacePath: p.workspacePath,
|
|
5465
5755
|
serverName: s.serverName
|
|
5466
5756
|
}, replaceProject(p.workspacePath));
|
|
5467
|
-
|
|
5757
|
+
/**
|
|
5758
|
+
* 保存全局 MCP:成功后由回调关闭子面板。
|
|
5759
|
+
* 作者 ddj 2026年08月27号
|
|
5760
|
+
* @param {Function} close 关闭新增表单回调
|
|
5761
|
+
*/
|
|
5762
|
+
const saveGlobal = (close) => {
|
|
5468
5763
|
setBusy("save");
|
|
5469
5764
|
setError("");
|
|
5470
5765
|
rpc("mcp.save", { config: configOf(draft) }).then(finish).then((result) => {
|
|
5471
5766
|
setServers((old) => old.some((s) => s.id === result.server.id) ? old.map((s) => s.id === result.server.id ? result.server : s) : old.concat(result.server));
|
|
5472
5767
|
setDraft(EMPTY);
|
|
5473
|
-
|
|
5768
|
+
close();
|
|
5474
5769
|
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
5475
5770
|
};
|
|
5476
|
-
|
|
5477
|
-
|
|
5771
|
+
/**
|
|
5772
|
+
* 保存项目 MCP:成功后由回调关闭子面板。
|
|
5773
|
+
* 作者 ddj 2026年08月27号
|
|
5774
|
+
* @param {string} workspacePath 目标项目路径
|
|
5775
|
+
* @param {Function} close 关闭新增表单回调
|
|
5776
|
+
*/
|
|
5777
|
+
const saveProject = (workspacePath, close) => {
|
|
5478
5778
|
setBusy("project-save");
|
|
5479
5779
|
setError("");
|
|
5480
5780
|
const config = configOf(draft);
|
|
5481
5781
|
rpc("mcp.projectSave", {
|
|
5482
|
-
workspacePath
|
|
5782
|
+
workspacePath,
|
|
5483
5783
|
serverName: config.serverName,
|
|
5484
5784
|
config
|
|
5485
5785
|
}).then(finish).then((result) => {
|
|
5486
|
-
setProjects((old) => old.map((p) => p.workspacePath ===
|
|
5786
|
+
setProjects((old) => old.map((p) => p.workspacePath === workspacePath ? result.project : p));
|
|
5487
5787
|
setDraft(EMPTY);
|
|
5488
|
-
|
|
5788
|
+
close();
|
|
5489
5789
|
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
5490
5790
|
};
|
|
5491
5791
|
const edit = (key, value) => setDraft((old) => ({
|
|
5492
5792
|
...old,
|
|
5493
5793
|
[key]: value
|
|
5494
5794
|
}));
|
|
5495
|
-
const
|
|
5496
|
-
const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
|
|
5497
|
-
const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
|
|
5498
|
-
projects,
|
|
5499
|
-
value: selectedPath,
|
|
5500
|
-
onChange: setSelectedPath
|
|
5501
|
-
}), selectedProject ? react.default.createElement(ProjectGroup, {
|
|
5502
|
-
project: selectedProject,
|
|
5503
|
-
busy,
|
|
5504
|
-
onAdd: (project) => {
|
|
5505
|
-
setDraft(EMPTY);
|
|
5506
|
-
setProjectForm({
|
|
5507
|
-
workspacePath: project.workspacePath,
|
|
5508
|
-
title: project.title
|
|
5509
|
-
});
|
|
5510
|
-
},
|
|
5511
|
-
onRefresh: refreshProject,
|
|
5512
|
-
onToggle: toggleProject,
|
|
5513
|
-
onRemove: removeProject
|
|
5514
|
-
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
|
|
5795
|
+
const resetDraft = () => setDraft(EMPTY);
|
|
5515
5796
|
let body;
|
|
5516
5797
|
if (loading) body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取 MCP 服务…");
|
|
5517
5798
|
else if (tab === "general") body = react.default.createElement(GeneralSettings, { registry: openerRegistry });
|
|
5518
5799
|
else if (tab === "keys") body = react.default.createElement(KeybindingsPanel, null);
|
|
5519
|
-
else if (tab === "
|
|
5520
|
-
|
|
5521
|
-
|
|
5522
|
-
onRefresh: refreshGlobal,
|
|
5523
|
-
onToggle: toggleGlobal,
|
|
5524
|
-
onRemove: removeGlobal
|
|
5525
|
-
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP"));
|
|
5526
|
-
else if (tab === "projects") body = projectBody;
|
|
5527
|
-
else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
|
|
5528
|
-
else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
|
|
5529
|
-
else if (tab === "perf") body = react.default.createElement(PerfSettings, null);
|
|
5530
|
-
else body = marketBody;
|
|
5531
|
-
const form = showForm ? react.default.createElement(McpForm, {
|
|
5532
|
-
title: "添加全局 MCP",
|
|
5533
|
-
draft,
|
|
5800
|
+
else if (tab === "mcp") body = react.default.createElement(McpManagePanel, {
|
|
5801
|
+
servers,
|
|
5802
|
+
projects,
|
|
5534
5803
|
busy,
|
|
5535
|
-
edit,
|
|
5536
|
-
save: saveGlobal,
|
|
5537
|
-
close: () => setShowForm(false)
|
|
5538
|
-
}) : projectForm ? react.default.createElement(McpForm, {
|
|
5539
|
-
title: "添加项目 MCP · " + projectForm.title,
|
|
5540
5804
|
draft,
|
|
5541
|
-
busy,
|
|
5542
5805
|
edit,
|
|
5543
|
-
|
|
5544
|
-
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
5552
|
-
}
|
|
5806
|
+
resetDraft,
|
|
5807
|
+
saveGlobal: (close) => saveGlobal(close),
|
|
5808
|
+
saveProject: (workspacePath, close) => saveProject(workspacePath, close),
|
|
5809
|
+
refreshGlobal,
|
|
5810
|
+
toggleGlobal,
|
|
5811
|
+
removeGlobal,
|
|
5812
|
+
refreshProject,
|
|
5813
|
+
toggleProject,
|
|
5814
|
+
removeProject
|
|
5815
|
+
});
|
|
5816
|
+
else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
|
|
5817
|
+
else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
|
|
5818
|
+
else body = react.default.createElement(PerfSettings, null);
|
|
5819
|
+
return react.default.createElement("section", { className: "vsm-mcp-page" }, react.default.createElement("header", { className: "vsm-mcp-header" }, react.default.createElement("div", null, react.default.createElement("h2", null, "VSCodeMode"), react.default.createElement("p", null, "管理当前 profile 与各项目的 Model Context Protocol 服务。"))), react.default.createElement("nav", { className: "vsm-mcp-tabs" }, react.default.createElement("button", {
|
|
5553
5820
|
className: tab === "general" ? "active" : "",
|
|
5554
5821
|
onClick: () => setTab("general")
|
|
5555
5822
|
}, "通用"), react.default.createElement("button", {
|
|
5556
5823
|
className: tab === "keys" ? "active" : "",
|
|
5557
5824
|
onClick: () => setTab("keys")
|
|
5558
5825
|
}, "快捷键"), react.default.createElement("button", {
|
|
5559
|
-
className: tab === "
|
|
5560
|
-
onClick: () => setTab("
|
|
5561
|
-
}, "
|
|
5562
|
-
className: tab === "projects" ? "active" : "",
|
|
5563
|
-
onClick: () => setTab("projects")
|
|
5564
|
-
}, "项目 MCP"), react.default.createElement("button", {
|
|
5826
|
+
className: tab === "mcp" ? "active" : "",
|
|
5827
|
+
onClick: () => setTab("mcp")
|
|
5828
|
+
}, "MCP 管理"), react.default.createElement("button", {
|
|
5565
5829
|
className: tab === "lsp" ? "active" : "",
|
|
5566
5830
|
onClick: () => setTab("lsp")
|
|
5567
5831
|
}, "语言服务器"), react.default.createElement("button", {
|
|
5568
5832
|
className: tab === "perf" ? "active" : "",
|
|
5569
5833
|
onClick: () => setTab("perf")
|
|
5570
5834
|
}, "性能优化"), react.default.createElement("button", {
|
|
5571
|
-
className: tab === "market" ? "active" : "",
|
|
5572
|
-
onClick: () => setTab("market")
|
|
5573
|
-
}, "MCP 市场"), react.default.createElement("button", {
|
|
5574
5835
|
className: tab === "compat" ? "active" : "",
|
|
5575
5836
|
onClick: () => setTab("compat")
|
|
5576
|
-
}, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body
|
|
5837
|
+
}, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body);
|
|
5577
5838
|
}
|
|
5578
5839
|
/** MCP 新增表单(全局/项目共用)。 */
|
|
5579
5840
|
function McpForm({ title, draft, busy, edit, save, close }) {
|