dsh-vscode-mode 0.1.57 → 0.1.59
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 +551 -31
- package/lib/client.js.map +1 -1
- package/lib/index.js +561 -98
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ai/inline.ts +192 -0
- package/src/ai/llmTypes.ts +83 -0
- package/src/ai/rpc.ts +35 -0
- package/src/capture.ts +2 -1
- package/src/client/ai/inlineProvider.ts +190 -0
- package/src/client/compat.ts +3 -2
- package/src/client/externalOpen.ts +3 -2
- package/src/client/index.ts +7 -6
- package/src/client/log.ts +10 -0
- package/src/client/sidebarBridge.ts +6 -5
- package/src/client/state/records.ts +1 -1
- package/src/client/state/regions.ts +13 -6
- package/src/client/ui/AiSettings.ts +104 -0
- package/src/client/ui/EditorView.ts +65 -4
- package/src/client/ui/McpSettings.ts +3 -1
- package/src/debugLog.ts +84 -0
- package/src/fileOpenSettings.ts +70 -8
- package/src/host-deps.d.ts +3 -0
- package/src/index.ts +21 -14
- package/src/log.ts +19 -0
- package/src/mcpIsolation.ts +2 -1
- package/src/rpc.ts +10 -76
- package/src/shared/ai.ts +158 -0
- package/src/shared/diff.ts +18 -0
- package/src/shared/logger.ts +114 -0
- package/src/shared/rpc.ts +9 -0
- package/src/store.ts +8 -4
- package/src/workspace.ts +2 -1
package/lib/client.js
CHANGED
|
@@ -107,6 +107,92 @@ window.__ModuleLoader__.load({
|
|
|
107
107
|
} catch (e) {}
|
|
108
108
|
}
|
|
109
109
|
//#endregion
|
|
110
|
+
//#region src/shared/logger.ts
|
|
111
|
+
/**
|
|
112
|
+
* dsh-vscode-mode — 统一日志核心(双面共享,平台中立)。
|
|
113
|
+
* 输出规范:
|
|
114
|
+
* - 格式:`[dsh-vscode-mode][:scope] 消息`;前缀只在模块内部拼接,业务代码禁止手写前缀
|
|
115
|
+
* - 级别语义:debug=诊断明细 / info=装配与路由里程碑 / warn=降级与兼容回退 / error=失败
|
|
116
|
+
* - 出口:host 面经 bindHostLog(ctx) 绑定 ctx.logger(缺失回退 console);client 面固定 console
|
|
117
|
+
* - 诊断文件通道(edrv.debug → ~/.dsh/dsh-vscode-mode/logs/)见 debugLog.ts,与本模块前缀共用
|
|
118
|
+
* 新增日志一律 `import { log } from '../log.js'`(client 为 './log.js')后调 log.debug/info/warn/error;
|
|
119
|
+
* 子域日志用 log.child('scope'),禁止另起 console。
|
|
120
|
+
* 作者 ddj 2026-09-08
|
|
121
|
+
*/
|
|
122
|
+
/** 插件统一日志前缀(与包安装名一致)。 */
|
|
123
|
+
const LOG_PREFIX = "[dsh-vscode-mode]";
|
|
124
|
+
/** 默认级别到 console 方法名的映射。 */
|
|
125
|
+
const CONSOLE_METHODS = {
|
|
126
|
+
debug: "debug",
|
|
127
|
+
info: "info",
|
|
128
|
+
warn: "warn",
|
|
129
|
+
error: "error"
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* 默认出口:console(级别直映方法;方法缺失时静默丢弃,不抛错)。
|
|
133
|
+
* @author ddj 2026年09月08号
|
|
134
|
+
* @returns console 出口
|
|
135
|
+
*/
|
|
136
|
+
function consoleSink() {
|
|
137
|
+
return (level, line) => {
|
|
138
|
+
const emit = console[CONSOLE_METHODS[level]];
|
|
139
|
+
if (typeof emit === "function") emit.call(console, line);
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 拼一行日志:`[dsh-vscode-mode][:scope] 消息`(scope 为空省略冒号段)。
|
|
144
|
+
* @author ddj 2026年09月08号
|
|
145
|
+
* @param scope 子域(可空)
|
|
146
|
+
* @param message 消息文本
|
|
147
|
+
* @returns 整行文本
|
|
148
|
+
*/
|
|
149
|
+
function formatLine(scope, message) {
|
|
150
|
+
const text = String(message);
|
|
151
|
+
return scope ? LOG_PREFIX + ":" + scope + " " + text : LOG_PREFIX + " " + text;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 日志器工厂(内部):emit 时读共享出口状态,保证父 bind 后子实例跟随。
|
|
155
|
+
* @author ddj 2026年09月08号
|
|
156
|
+
* @param state 共享出口状态
|
|
157
|
+
* @param scope 子域文本(可空)
|
|
158
|
+
* @returns 日志器
|
|
159
|
+
*/
|
|
160
|
+
function makeLogger(state, scope) {
|
|
161
|
+
const emit = (level, message) => state.sink(level, formatLine(scope, message));
|
|
162
|
+
return {
|
|
163
|
+
debug: (message) => emit("debug", message),
|
|
164
|
+
info: (message) => emit("info", message),
|
|
165
|
+
warn: (message) => emit("warn", message),
|
|
166
|
+
error: (message) => emit("error", message),
|
|
167
|
+
bind(next) {
|
|
168
|
+
state.sink = next;
|
|
169
|
+
},
|
|
170
|
+
child(next) {
|
|
171
|
+
return makeLogger(state, scope ? scope + "." + next : next);
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 创建插件日志器(默认 console 出口;host 面单例由 bindHostLog 换到 ctx.logger)。
|
|
177
|
+
* @author ddj 2026年09月08号
|
|
178
|
+
* @param sink 初始出口(缺省 console)
|
|
179
|
+
* @param scope 初始子域(业务方一般经 child 派生,不直接传)
|
|
180
|
+
* @returns 日志器
|
|
181
|
+
*/
|
|
182
|
+
function createLogger(sink = consoleSink(), scope = "") {
|
|
183
|
+
return makeLogger({ sink }, scope);
|
|
184
|
+
}
|
|
185
|
+
//#endregion
|
|
186
|
+
//#region src/client/log.ts
|
|
187
|
+
/**
|
|
188
|
+
* dsh-vscode-mode client — 日志单例(全 client 面唯一日志入口)。
|
|
189
|
+
* client 无 ctx.logger 服务,固定 console 出口;格式/级别语义与 host 面一致(shared/logger.ts)。
|
|
190
|
+
* 用法:`import { log } from './log.js'` → `log.debug/info/warn/error(msg)`;子域用 `log.child('scope')`。
|
|
191
|
+
* 作者 ddj 2026-09-08
|
|
192
|
+
*/
|
|
193
|
+
/** client 面插件日志单例。 */
|
|
194
|
+
const log = createLogger();
|
|
195
|
+
//#endregion
|
|
110
196
|
//#region src/client/sidebarBridge.ts
|
|
111
197
|
/**
|
|
112
198
|
* dsh-vscode-mode client — 侧边栏编辑区桥:可选探测 dsh-better-sidebar 的
|
|
@@ -224,7 +310,7 @@ window.__ModuleLoader__.load({
|
|
|
224
310
|
try {
|
|
225
311
|
return ensureSideEditor(path, focusDiff) === true;
|
|
226
312
|
} catch (error) {
|
|
227
|
-
|
|
313
|
+
log.warn("侧栏编辑器打开失败(" + String(error) + "),已回退");
|
|
228
314
|
return false;
|
|
229
315
|
}
|
|
230
316
|
}
|
|
@@ -260,7 +346,7 @@ window.__ModuleLoader__.load({
|
|
|
260
346
|
try {
|
|
261
347
|
registerLegacyFallback();
|
|
262
348
|
} catch (error) {
|
|
263
|
-
|
|
349
|
+
log.warn("回退注册失败(" + String(error) + ")");
|
|
264
350
|
}
|
|
265
351
|
}
|
|
266
352
|
return false;
|
|
@@ -282,7 +368,7 @@ window.__ModuleLoader__.load({
|
|
|
282
368
|
cwd: scope?.cwd
|
|
283
369
|
});
|
|
284
370
|
} catch (error) {
|
|
285
|
-
|
|
371
|
+
log.warn("openTab 失败(" + String(error) + ")");
|
|
286
372
|
return false;
|
|
287
373
|
}
|
|
288
374
|
if (sideEditorMounted) window.dispatchEvent(new CustomEvent("edrv:open-editor", { detail: {
|
|
@@ -303,13 +389,13 @@ window.__ModuleLoader__.load({
|
|
|
303
389
|
component: renderTab
|
|
304
390
|
});
|
|
305
391
|
} catch (error) {
|
|
306
|
-
|
|
392
|
+
log.warn("侧边栏 Tab 注册失败(" + String(error) + ")");
|
|
307
393
|
if (!fallbackInstalled) {
|
|
308
394
|
fallbackInstalled = true;
|
|
309
395
|
try {
|
|
310
396
|
registerLegacyFallback();
|
|
311
397
|
} catch (inner) {
|
|
312
|
-
|
|
398
|
+
log.warn("回退注册失败(" + String(inner) + ")");
|
|
313
399
|
}
|
|
314
400
|
}
|
|
315
401
|
return () => {};
|
|
@@ -1763,6 +1849,22 @@ window.__ModuleLoader__.load({
|
|
|
1763
1849
|
if (idx === 0 && record.callHunk && typeof record.callHunk.newText === "string") return record.callHunk;
|
|
1764
1850
|
return null;
|
|
1765
1851
|
}
|
|
1852
|
+
/**
|
|
1853
|
+
* 差异比较口径归一化:剥离 UTF-8 BOM、统一 CRLF 为 LF。
|
|
1854
|
+
* 背景:外部工具(pwsh/编辑器)可能改变 BOM/行尾,导致 after 指纹与 hunk
|
|
1855
|
+
* 定位在内容语义未变的情况下误报 conflict,把待确认修改从 UI 排除。
|
|
1856
|
+
* @author ddj 2026年09月09号
|
|
1857
|
+
*/
|
|
1858
|
+
function normalizeForCompare(text) {
|
|
1859
|
+
return text.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n");
|
|
1860
|
+
}
|
|
1861
|
+
/** 归一化一个 hunk 的两侧文本(与 normalizeForCompare 同口径)。 */
|
|
1862
|
+
function normalizeHunk(hunk) {
|
|
1863
|
+
return {
|
|
1864
|
+
oldText: hunk.oldText === null ? null : normalizeForCompare(hunk.oldText),
|
|
1865
|
+
newText: normalizeForCompare(hunk.newText)
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1766
1868
|
/** splitLines 的空文本语义:真实空文件没有一行变更内容。 */
|
|
1767
1869
|
function splitLines(text) {
|
|
1768
1870
|
return text.length ? text.split("\n") : [];
|
|
@@ -1893,7 +1995,7 @@ window.__ModuleLoader__.load({
|
|
|
1893
1995
|
}
|
|
1894
1996
|
/** 记录是否仍有待处理差异(superseded / 全部已决策 = false)。 */
|
|
1895
1997
|
function isRecPending(rec) {
|
|
1896
|
-
if (!rec || rec.superseded === true
|
|
1998
|
+
if (!rec || rec.superseded === true) return false;
|
|
1897
1999
|
const perHunk = Array.isArray(rec.decisions?.perHunk) ? rec.decisions.perHunk : [];
|
|
1898
2000
|
if (perHunk.length) {
|
|
1899
2001
|
for (let i = 0; i < perHunk.length; i++) if (perHunk[i] !== "accepted" && perHunk[i] !== "rejected" && !noopHunk(rec, (rec.hunks || [])[i])) return true;
|
|
@@ -1964,11 +2066,18 @@ window.__ModuleLoader__.load({
|
|
|
1964
2066
|
shift: prefix
|
|
1965
2067
|
};
|
|
1966
2068
|
}
|
|
1967
|
-
/**
|
|
2069
|
+
/**
|
|
2070
|
+
* 计算文件内各差异区域(行范围 + old/new + 状态),用于行内绿标注与 DiffBox。
|
|
2071
|
+
* 定位统一基于归一化文本(剥 BOM、CRLF→LF):外部工具可能改变行尾/BOM,
|
|
2072
|
+
* 与 edit 工具的 LF hunk 口径不一致会导致定位失败(差异被误标 stale)。
|
|
2073
|
+
* 行号按 \n 计数,归一化不改变行号,展示语义不变。
|
|
2074
|
+
* @author ddj 2026年09月09号
|
|
2075
|
+
*/
|
|
1968
2076
|
function diffRegions(records, content) {
|
|
1969
2077
|
const regions = [];
|
|
1970
2078
|
if (content === null) return regions;
|
|
1971
|
-
const
|
|
2079
|
+
const normalized = normalizeForCompare(content);
|
|
2080
|
+
const lines = splitLines(normalized);
|
|
1972
2081
|
for (const rec of records) {
|
|
1973
2082
|
if (rec.create) {
|
|
1974
2083
|
for (let i = 0; i < rec.hunks.length; i++) {
|
|
@@ -1995,10 +2104,10 @@ window.__ModuleLoader__.load({
|
|
|
1995
2104
|
const hunk = preciseHunk(rec, i);
|
|
1996
2105
|
if (hunk && !noopHunk(rec, hunk)) entries.push({
|
|
1997
2106
|
idx: i,
|
|
1998
|
-
hunk
|
|
2107
|
+
hunk: normalizeHunk(hunk)
|
|
1999
2108
|
});
|
|
2000
2109
|
}
|
|
2001
|
-
const locations = locateHunks(
|
|
2110
|
+
const locations = locateHunks(normalized, entries.map((entry) => entry.hunk));
|
|
2002
2111
|
for (let i = 0; i < entries.length; i++) {
|
|
2003
2112
|
const entry = entries[i];
|
|
2004
2113
|
const location = locations[i];
|
|
@@ -2017,7 +2126,7 @@ window.__ModuleLoader__.load({
|
|
|
2017
2126
|
});
|
|
2018
2127
|
continue;
|
|
2019
2128
|
}
|
|
2020
|
-
const start = countLinesBefore(
|
|
2129
|
+
const start = countLinesBefore(normalized, location.start) + 1;
|
|
2021
2130
|
const trimmed = trimCommonLines(entry.hunk.oldText === null ? [] : entry.hunk.oldText.split("\n"), entry.hunk.newText.split("\n"));
|
|
2022
2131
|
const regionStart = start + trimmed.shift;
|
|
2023
2132
|
regions.push({
|
|
@@ -3684,7 +3793,7 @@ window.__ModuleLoader__.load({
|
|
|
3684
3793
|
tokenTypes: [...LSP_SEMANTIC_TOKEN_TYPES],
|
|
3685
3794
|
tokenModifiers: [...LSP_SEMANTIC_TOKEN_MODIFIERS]
|
|
3686
3795
|
};
|
|
3687
|
-
let registered = false;
|
|
3796
|
+
let registered$1 = false;
|
|
3688
3797
|
const disposables = [];
|
|
3689
3798
|
/** 目标文件打开并定位(复用现有 openFileAt 的 edrv:open-editor 事件通道)。 */
|
|
3690
3799
|
function openAt(path, line, column) {
|
|
@@ -3701,8 +3810,8 @@ window.__ModuleLoader__.load({
|
|
|
3701
3810
|
}
|
|
3702
3811
|
/** 注册全部 Monaco LSP provider 与文档跟踪(幂等)。 */
|
|
3703
3812
|
function registerLspProviders(monaco) {
|
|
3704
|
-
if (registered) return;
|
|
3705
|
-
registered = true;
|
|
3813
|
+
if (registered$1) return;
|
|
3814
|
+
registered$1 = true;
|
|
3706
3815
|
const attachModel = (model) => {
|
|
3707
3816
|
if (!model || model.uri.scheme !== "edrv") return;
|
|
3708
3817
|
const path = pathOfModel(model);
|
|
@@ -3947,7 +4056,7 @@ window.__ModuleLoader__.load({
|
|
|
3947
4056
|
* 数据来源于语义 token(每 model+version 缓存 60s),不做逐词 LSP 查询。
|
|
3948
4057
|
* 作者 ddj 2026-09-02
|
|
3949
4058
|
*/
|
|
3950
|
-
const CACHE_TTL_MS = 6e4;
|
|
4059
|
+
const CACHE_TTL_MS$1 = 6e4;
|
|
3951
4060
|
/**
|
|
3952
4061
|
* 给一个 Monaco 编辑器绑定 Ctrl+hover 下划线提示(幂等)。
|
|
3953
4062
|
* @author ddj 2026年09月02号
|
|
@@ -3977,7 +4086,7 @@ window.__ModuleLoader__.load({
|
|
|
3977
4086
|
const rangesOf = async (model) => {
|
|
3978
4087
|
const path = pathOfModel(model);
|
|
3979
4088
|
const version = model.getVersionId?.() ?? 0;
|
|
3980
|
-
if (cache.path === path && cache.version === version && cache.ranges && Date.now() - cache.at < CACHE_TTL_MS) return cache.ranges;
|
|
4089
|
+
if (cache.path === path && cache.version === version && cache.ranges && Date.now() - cache.at < CACHE_TTL_MS$1) return cache.ranges;
|
|
3981
4090
|
const res = await fetchSemanticTokens(path, model.getValue()).catch(() => null);
|
|
3982
4091
|
const ranges = res && Array.isArray(res.data) ? decodeSemanticTokens(res.data) : [];
|
|
3983
4092
|
cache = {
|
|
@@ -4054,6 +4163,237 @@ window.__ModuleLoader__.load({
|
|
|
4054
4163
|
refreshStatus(true);
|
|
4055
4164
|
}
|
|
4056
4165
|
//#endregion
|
|
4166
|
+
//#region src/shared/ai.ts
|
|
4167
|
+
/**
|
|
4168
|
+
* dsh-vscode-mode shared — AI 内联补全契约与纯函数(双面,禁 node/react)。
|
|
4169
|
+
* 只放"过 RPC 的载荷形状"与无副作用变换:窗口裁剪、prompt 拼装、输出清理。
|
|
4170
|
+
* host 与语言模型之间的流式交互在 host 侧 src/ai/inline.ts。
|
|
4171
|
+
* 作者 ddj
|
|
4172
|
+
*/
|
|
4173
|
+
/** 上下文窗口:前缀字符上限(含未保存编辑的当前文档)。 */
|
|
4174
|
+
const AI_PREFIX_MAX = 2e3;
|
|
4175
|
+
/**
|
|
4176
|
+
* 按 AI_PREFIX_MAX/AI_SUFFIX_MAX 裁剪前后缀(client 侧发请求前调用)。
|
|
4177
|
+
* @author ddj
|
|
4178
|
+
* @param prefix 光标前全文
|
|
4179
|
+
* @param suffix 光标后全文
|
|
4180
|
+
* @returns 裁剪后的窗口
|
|
4181
|
+
*/
|
|
4182
|
+
function trimInlineWindow(prefix, suffix) {
|
|
4183
|
+
const pre = typeof prefix === "string" ? prefix : "";
|
|
4184
|
+
const suf = typeof suffix === "string" ? suffix : "";
|
|
4185
|
+
return {
|
|
4186
|
+
prefix: pre.length > 2e3 ? pre.slice(pre.length - AI_PREFIX_MAX) : pre,
|
|
4187
|
+
suffix: suf.length > 500 ? suf.slice(0, 500) : suf
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
/**
|
|
4191
|
+
* 是否值得发起补全请求:前缀达标且非纯空白(空行/行首不请求)。
|
|
4192
|
+
* @author ddj
|
|
4193
|
+
* @param prefix 光标前文本
|
|
4194
|
+
* @returns 是否可请求
|
|
4195
|
+
*/
|
|
4196
|
+
function inlineWorth(prefix) {
|
|
4197
|
+
const pre = typeof prefix === "string" ? prefix : "";
|
|
4198
|
+
if (pre.length < 4) return false;
|
|
4199
|
+
return pre.trim().length > 0;
|
|
4200
|
+
}
|
|
4201
|
+
//#endregion
|
|
4202
|
+
//#region src/client/ai/inlineProvider.ts
|
|
4203
|
+
/**
|
|
4204
|
+
* dsh-vscode-mode client — AI 内联补全(ghost text)provider。
|
|
4205
|
+
* 速度设计:内部 150ms 去抖(Monaco 逐键调用 provider,不逐键打 LLM)、
|
|
4206
|
+
* 在飞取消(新请求 abort 旧的)+ 序号守卫(过期响应丢弃)、
|
|
4207
|
+
* 位置+前缀缓存(dismiss 后同位置重查不重复计费)、前后缀窗口裁剪。
|
|
4208
|
+
* 状态经 edrv:ai-status 事件广播给编辑器底部状态栏;
|
|
4209
|
+
* 差异审查期间静默(edrv 差异块存在时返回空,不与 Keep/Undo 浮层打架)。
|
|
4210
|
+
* 作者 ddj
|
|
4211
|
+
*/
|
|
4212
|
+
/**
|
|
4213
|
+
* 广播补全状态给编辑器底部状态栏(EditorView 监听 edrv:ai-status 渲染)。
|
|
4214
|
+
* @author ddj
|
|
4215
|
+
* @param state busy=请求中 ok=完成(detail.ms 耗时) error=失败(detail.note) idle=就绪 config=开关变化
|
|
4216
|
+
* @param detail 附加信息
|
|
4217
|
+
*/
|
|
4218
|
+
function emitAiStatus(state, detail) {
|
|
4219
|
+
try {
|
|
4220
|
+
window.dispatchEvent(new CustomEvent("edrv:ai-status", { detail: {
|
|
4221
|
+
state,
|
|
4222
|
+
detail
|
|
4223
|
+
} }));
|
|
4224
|
+
} catch {}
|
|
4225
|
+
}
|
|
4226
|
+
/** 去抖等待(毫秒):Monaco 逐键调用 provider,静默该时长才真正发 RPC;
|
|
4227
|
+
* 网关 TTFT 本身 3.5s+(debug 实证),client 侧去抖压到 150ms 不再叠加明显延迟。 */
|
|
4228
|
+
const DEBOUNCE_MS$1 = 150;
|
|
4229
|
+
/** 缓存容量上限(LRU 超限即整体清空;键空间小,够用)。 */
|
|
4230
|
+
const CACHE_MAX = 30;
|
|
4231
|
+
/** 缓存 TTL(毫秒)。 */
|
|
4232
|
+
const CACHE_TTL_MS = 6e4;
|
|
4233
|
+
let registered = false;
|
|
4234
|
+
let seq = 0;
|
|
4235
|
+
let inFlight = null;
|
|
4236
|
+
let debounceTimer = null;
|
|
4237
|
+
const cache = /* @__PURE__ */ new Map();
|
|
4238
|
+
/** 上次开关状态(configUpdate 后经事件刷新,关闭时立即静默)。 */
|
|
4239
|
+
let enabled = false;
|
|
4240
|
+
/** 供设置面板保存后同步开关状态(不重载页面即时生效)。 */
|
|
4241
|
+
function setAiInlineEnabled(value) {
|
|
4242
|
+
enabled = value === true;
|
|
4243
|
+
emitAiStatus("config", { enabled });
|
|
4244
|
+
if (!enabled) {
|
|
4245
|
+
seq += 1;
|
|
4246
|
+
cache.clear();
|
|
4247
|
+
}
|
|
4248
|
+
}
|
|
4249
|
+
/** 当前开关(EditorView 装配时决定是否注册)。 */
|
|
4250
|
+
function aiInlineEnabled() {
|
|
4251
|
+
return enabled;
|
|
4252
|
+
}
|
|
4253
|
+
/** 初始化开关(启动时从 configGet 拉一次)。 */
|
|
4254
|
+
async function initAiInlineState() {
|
|
4255
|
+
try {
|
|
4256
|
+
const res = await rpc("edrv.ai.configGet", {});
|
|
4257
|
+
setAiInlineEnabled(res?.ok && res.enabled === true);
|
|
4258
|
+
} catch {
|
|
4259
|
+
setAiInlineEnabled(false);
|
|
4260
|
+
}
|
|
4261
|
+
}
|
|
4262
|
+
/**
|
|
4263
|
+
* 缓存键:路径 + 行:列 + 前缀尾 24 字符(同位置不同文本区分)。
|
|
4264
|
+
* @author ddj
|
|
4265
|
+
*/
|
|
4266
|
+
function cacheKey(path, line, column, prefix) {
|
|
4267
|
+
return path + ":" + line + ":" + column + ":" + String(prefix || "").slice(-24);
|
|
4268
|
+
}
|
|
4269
|
+
/**
|
|
4270
|
+
* 差异审查期间静默:编辑器上存在 AI 补全应避让的 pending 差异装饰时返回 true。
|
|
4271
|
+
* 通过编辑器实例上的 edrv 差异装饰类名判定,缺失视为无差异。
|
|
4272
|
+
* @author ddj
|
|
4273
|
+
*/
|
|
4274
|
+
function diffPending(editor) {
|
|
4275
|
+
try {
|
|
4276
|
+
return ((editor?.getModel?.())?.getAllDecorations?.() ?? []).some((d) => String(d?.options?.className || "").includes("edrv-mn-"));
|
|
4277
|
+
} catch {
|
|
4278
|
+
return false;
|
|
4279
|
+
}
|
|
4280
|
+
}
|
|
4281
|
+
/**
|
|
4282
|
+
* 注册 AI 内联补全 provider(幂等;Monaco 就绪后调用一次)。
|
|
4283
|
+
* @author ddj
|
|
4284
|
+
* @param monaco window.monaco
|
|
4285
|
+
*/
|
|
4286
|
+
function registerAiInline(monaco) {
|
|
4287
|
+
if (registered || !monaco?.languages?.registerInlineCompletionsProvider) return;
|
|
4288
|
+
registered = true;
|
|
4289
|
+
monaco.languages.registerInlineCompletionsProvider("*", {
|
|
4290
|
+
async provideInlineCompletions(model, position, context, token) {
|
|
4291
|
+
const t0 = Date.now();
|
|
4292
|
+
if (!enabled || token?.isCancellationRequested) return { items: [] };
|
|
4293
|
+
const path = decodeURIComponent(String(model?.uri?.path || "").replace(/^\//, ""));
|
|
4294
|
+
if (!path || model.uri.scheme !== "edrv") return { items: [] };
|
|
4295
|
+
const editor = monacoRef?.activeEditor;
|
|
4296
|
+
if (editor && diffPending(editor)) return { items: [] };
|
|
4297
|
+
const offset = model.getOffsetAt(position);
|
|
4298
|
+
const value = model.getValue();
|
|
4299
|
+
const trimmed = trimInlineWindow(value.slice(0, offset), value.slice(offset));
|
|
4300
|
+
if (!inlineWorth(trimmed.prefix)) return { items: [] };
|
|
4301
|
+
const key = cacheKey(path, position.lineNumber, position.column, trimmed.prefix);
|
|
4302
|
+
const hit = cache.get(key);
|
|
4303
|
+
if (hit && Date.now() - hit.at < CACHE_TTL_MS) return hit.items.length ? { items: hit.items } : { items: [] };
|
|
4304
|
+
return new Promise((resolve) => {
|
|
4305
|
+
seq += 1;
|
|
4306
|
+
const mySeq = seq;
|
|
4307
|
+
if (inFlight) {
|
|
4308
|
+
try {
|
|
4309
|
+
inFlight.abort();
|
|
4310
|
+
} catch {}
|
|
4311
|
+
inFlight = null;
|
|
4312
|
+
}
|
|
4313
|
+
if (debounceTimer) clearTimeout(debounceTimer);
|
|
4314
|
+
debounceTimer = setTimeout(async () => {
|
|
4315
|
+
debounceTimer = null;
|
|
4316
|
+
if (mySeq !== seq || token?.isCancellationRequested) {
|
|
4317
|
+
resolve({ items: [] });
|
|
4318
|
+
return;
|
|
4319
|
+
}
|
|
4320
|
+
const controller = new AbortController();
|
|
4321
|
+
inFlight = controller;
|
|
4322
|
+
emitAiStatus("busy");
|
|
4323
|
+
try {
|
|
4324
|
+
const data = await (await fetch(RPC_PATH, {
|
|
4325
|
+
method: "POST",
|
|
4326
|
+
headers: { "content-type": "application/json" },
|
|
4327
|
+
body: JSON.stringify({
|
|
4328
|
+
method: "edrv.ai.inline",
|
|
4329
|
+
args: {
|
|
4330
|
+
path,
|
|
4331
|
+
prefix: trimmed.prefix,
|
|
4332
|
+
suffix: trimmed.suffix
|
|
4333
|
+
}
|
|
4334
|
+
}),
|
|
4335
|
+
signal: controller.signal
|
|
4336
|
+
})).json();
|
|
4337
|
+
if (mySeq !== seq || !data?.ok || typeof data.text !== "string" || !data.text) {
|
|
4338
|
+
cache.set(key, {
|
|
4339
|
+
items: [],
|
|
4340
|
+
at: Date.now()
|
|
4341
|
+
});
|
|
4342
|
+
if (data?.note) emitAiStatus("error", { note: String(data.note) });
|
|
4343
|
+
else emitAiStatus("idle");
|
|
4344
|
+
resolve({ items: [] });
|
|
4345
|
+
return;
|
|
4346
|
+
}
|
|
4347
|
+
const items = [{
|
|
4348
|
+
insertText: data.text,
|
|
4349
|
+
range: void 0
|
|
4350
|
+
}];
|
|
4351
|
+
cacheSet(key, items);
|
|
4352
|
+
emitAiStatus("ok", { ms: Date.now() - t0 });
|
|
4353
|
+
resolve({ items });
|
|
4354
|
+
} catch (error) {
|
|
4355
|
+
emitAiStatus("error", { note: String(error && error.message || error).slice(0, 80) });
|
|
4356
|
+
resolve({ items: [] });
|
|
4357
|
+
} finally {
|
|
4358
|
+
if (inFlight === controller) inFlight = null;
|
|
4359
|
+
}
|
|
4360
|
+
}, DEBOUNCE_MS$1);
|
|
4361
|
+
const guard = setInterval(() => {
|
|
4362
|
+
if (mySeq !== seq || token?.isCancellationRequested) {
|
|
4363
|
+
clearInterval(guard);
|
|
4364
|
+
if (debounceTimer) {
|
|
4365
|
+
clearTimeout(debounceTimer);
|
|
4366
|
+
debounceTimer = null;
|
|
4367
|
+
}
|
|
4368
|
+
resolve({ items: [] });
|
|
4369
|
+
} else if (!debounceTimer) clearInterval(guard);
|
|
4370
|
+
}, 120);
|
|
4371
|
+
});
|
|
4372
|
+
},
|
|
4373
|
+
freeInlineCompletions() {}
|
|
4374
|
+
});
|
|
4375
|
+
}
|
|
4376
|
+
/** 缓存写入(超限整体清空,LRU 简化)。 */
|
|
4377
|
+
function cacheSet(key, items) {
|
|
4378
|
+
if (cache.size >= CACHE_MAX) cache.clear();
|
|
4379
|
+
cache.set(key, {
|
|
4380
|
+
items,
|
|
4381
|
+
at: Date.now()
|
|
4382
|
+
});
|
|
4383
|
+
}
|
|
4384
|
+
/** EditorView ref 注入口(差异静默判定用当前编辑器)。 */
|
|
4385
|
+
let monacoRef = null;
|
|
4386
|
+
/** EditorView 装配后调用(注册 provider + 初始化开关)。 */
|
|
4387
|
+
function setupAiInline(monaco) {
|
|
4388
|
+
monacoRef = monaco && { activeEditor: null };
|
|
4389
|
+
if (!registered) registerAiInline(monaco);
|
|
4390
|
+
initAiInlineState();
|
|
4391
|
+
}
|
|
4392
|
+
/** EditorView 每次创建/切换编辑器实例时刷新引用。 */
|
|
4393
|
+
function trackAiEditor(editor) {
|
|
4394
|
+
if (monacoRef) monacoRef.activeEditor = editor;
|
|
4395
|
+
}
|
|
4396
|
+
//#endregion
|
|
4057
4397
|
//#region src/client/ui/EditorView.ts
|
|
4058
4398
|
/**
|
|
4059
4399
|
* dsh-vscode-mode client — EditorView:中央 VSCode 式文件编辑器(编排层)。
|
|
@@ -4093,6 +4433,12 @@ window.__ModuleLoader__.load({
|
|
|
4093
4433
|
const pdfHostRef = react.default.useRef(null);
|
|
4094
4434
|
const [status, setStatus] = react.default.useState("");
|
|
4095
4435
|
const [lspServers, setLspServers] = react.default.useState([]);
|
|
4436
|
+
const [aiStatus, setAiStatus] = react.default.useState({
|
|
4437
|
+
state: "idle",
|
|
4438
|
+
detail: null,
|
|
4439
|
+
enabled: false
|
|
4440
|
+
});
|
|
4441
|
+
const aiStatusTimer = react.default.useRef(null);
|
|
4096
4442
|
const [error, setError] = react.default.useState(null);
|
|
4097
4443
|
const [loadError, setLoadError] = react.default.useState(null);
|
|
4098
4444
|
const [loadStage, setLoadStage] = react.default.useState({
|
|
@@ -4564,6 +4910,39 @@ window.__ModuleLoader__.load({
|
|
|
4564
4910
|
clearInterval(timer);
|
|
4565
4911
|
};
|
|
4566
4912
|
}, [sessionId]);
|
|
4913
|
+
react.default.useEffect(() => {
|
|
4914
|
+
const apply = (state, detail) => {
|
|
4915
|
+
if (aiStatusTimer.current) {
|
|
4916
|
+
clearTimeout(aiStatusTimer.current);
|
|
4917
|
+
aiStatusTimer.current = null;
|
|
4918
|
+
}
|
|
4919
|
+
setAiStatus((prev) => ({
|
|
4920
|
+
state,
|
|
4921
|
+
detail: detail ?? null,
|
|
4922
|
+
enabled: state === "config" ? detail?.enabled === true : prev.enabled
|
|
4923
|
+
}));
|
|
4924
|
+
if (state === "ok") aiStatusTimer.current = setTimeout(() => setAiStatus((p) => p.state === "ok" ? {
|
|
4925
|
+
...p,
|
|
4926
|
+
state: "idle"
|
|
4927
|
+
} : p), 3e3);
|
|
4928
|
+
else if (state === "error") aiStatusTimer.current = setTimeout(() => setAiStatus((p) => p.state === "error" ? {
|
|
4929
|
+
...p,
|
|
4930
|
+
state: "idle"
|
|
4931
|
+
} : p), 1e4);
|
|
4932
|
+
};
|
|
4933
|
+
const onAiStatus = (e) => {
|
|
4934
|
+
if (e?.detail?.state) apply(e.detail.state, e.detail.detail);
|
|
4935
|
+
};
|
|
4936
|
+
const onAiConfig = (e) => apply("config", { enabled: e?.detail?.enabled === true });
|
|
4937
|
+
window.addEventListener("edrv:ai-status", onAiStatus);
|
|
4938
|
+
window.addEventListener("edrv:ai-config", onAiConfig);
|
|
4939
|
+
apply("config", { enabled: aiInlineEnabled() });
|
|
4940
|
+
return () => {
|
|
4941
|
+
window.removeEventListener("edrv:ai-status", onAiStatus);
|
|
4942
|
+
window.removeEventListener("edrv:ai-config", onAiConfig);
|
|
4943
|
+
if (aiStatusTimer.current) clearTimeout(aiStatusTimer.current);
|
|
4944
|
+
};
|
|
4945
|
+
}, []);
|
|
4567
4946
|
react.default.useEffect(() => {
|
|
4568
4947
|
const onOpen = (e) => {
|
|
4569
4948
|
const p = e?.detail?.path;
|
|
@@ -4848,7 +5227,9 @@ window.__ModuleLoader__.load({
|
|
|
4848
5227
|
}));
|
|
4849
5228
|
};
|
|
4850
5229
|
loadMonaco(onProgress).then((m) => {
|
|
4851
|
-
if (alive)
|
|
5230
|
+
if (!alive) return;
|
|
5231
|
+
setMonaco(m);
|
|
5232
|
+
setupAiInline(m);
|
|
4852
5233
|
}).catch((e) => {
|
|
4853
5234
|
if (alive) {
|
|
4854
5235
|
setMonacoErr(String(e?.message ?? e));
|
|
@@ -5048,7 +5429,8 @@ window.__ModuleLoader__.load({
|
|
|
5048
5429
|
renderWhitespace: "selection",
|
|
5049
5430
|
smoothScrolling: true,
|
|
5050
5431
|
cursorBlinking: "smooth",
|
|
5051
|
-
padding: { top: side ? 6 : 8 }
|
|
5432
|
+
padding: { top: side ? 6 : 8 },
|
|
5433
|
+
inlineSuggest: { enabled: true }
|
|
5052
5434
|
});
|
|
5053
5435
|
ed.onDidChangeModelContent(() => {
|
|
5054
5436
|
if (!ed.getModel() || programmaticRef.current) return;
|
|
@@ -5134,6 +5516,10 @@ window.__ModuleLoader__.load({
|
|
|
5134
5516
|
});
|
|
5135
5517
|
bindLspEditor(ed);
|
|
5136
5518
|
bindLspUnderline(ed, m);
|
|
5519
|
+
trackAiEditor(ed);
|
|
5520
|
+
ed.addCommand(m.KeyMod.Alt | m.KeyCode.Backslash, () => {
|
|
5521
|
+
ed.trigger("edrv-ai", "editor.action.inlineSuggest.trigger", null);
|
|
5522
|
+
});
|
|
5137
5523
|
const hideSoon = () => {
|
|
5138
5524
|
if (hoverEditorRef.current || hoverPanelRef.current || hideTimerRef.current) return;
|
|
5139
5525
|
hideTimerRef.current = setTimeout(() => {
|
|
@@ -5670,10 +6056,35 @@ window.__ModuleLoader__.load({
|
|
|
5670
6056
|
none: "未配置"
|
|
5671
6057
|
}[lspServer.source] ?? lspServer.source) : "LSP " + lspLanguage + " · 未启动";
|
|
5672
6058
|
const lspProgress = typeof lspServer?.progress === "number" ? Math.round(lspServer.progress) : null;
|
|
5673
|
-
const
|
|
5674
|
-
className: "edrv-
|
|
6059
|
+
const lspSeg = lspLanguage === "lua" || lspLanguage === "csharp" ? react.default.createElement("span", {
|
|
6060
|
+
className: "edrv-status-seg",
|
|
6061
|
+
style: {
|
|
6062
|
+
display: "inline-flex",
|
|
6063
|
+
alignItems: "center",
|
|
6064
|
+
gap: "10px",
|
|
6065
|
+
minWidth: 0,
|
|
6066
|
+
flex: "1 1 auto"
|
|
6067
|
+
},
|
|
5675
6068
|
title: lspServer?.progressMessage || lspLabel
|
|
5676
6069
|
}, react.default.createElement("span", { className: "edrv-lsp-status-dot " + (lspServer?.phase === "ready" ? "ready" : lspServer?.phase === "indexing" || lspServer?.phase === "starting" ? "busy" : "idle") }), react.default.createElement("span", { className: "edrv-sp-lsp" }, lspLabel), lspServer?.progressMessage ? react.default.createElement("span", { className: "edrv-sp-progress-message" }, lspServer.progressMessage) : null, lspProgress !== null ? react.default.createElement("span", { className: "edrv-sp-progress" }, lspProgress + "%") : null) : null;
|
|
6070
|
+
const aiEnabled = aiStatus.enabled;
|
|
6071
|
+
const aiState = aiStatus.state;
|
|
6072
|
+
const aiDotCls = aiState === "busy" ? "busy" : aiState === "ok" ? "ready" : aiState === "error" ? "idle" : "ready";
|
|
6073
|
+
const aiMsText = aiStatus.detail && aiStatus.detail.ms != null ? (aiStatus.detail.ms / 1e3).toFixed(1) + "s" : "";
|
|
6074
|
+
const aiNote = aiStatus.detail && aiStatus.detail.note ? String(aiStatus.detail.note) : "";
|
|
6075
|
+
const aiLabel = !aiEnabled ? "AI 补全 · 关" : aiState === "busy" ? "AI 补全请求中…" : aiState === "ok" ? "AI 补全完成" + (aiMsText ? " · " + aiMsText : "") : aiState === "error" ? "AI 补全失败 · " + aiNote.slice(0, 48) : "AI 补全就绪";
|
|
6076
|
+
const aiSeg = active && aiEnabled !== null ? react.default.createElement("span", {
|
|
6077
|
+
className: "edrv-status-seg",
|
|
6078
|
+
style: {
|
|
6079
|
+
display: "inline-flex",
|
|
6080
|
+
alignItems: "center",
|
|
6081
|
+
gap: "10px",
|
|
6082
|
+
minWidth: 0,
|
|
6083
|
+
flex: "0 0 auto"
|
|
6084
|
+
},
|
|
6085
|
+
title: aiState === "error" && aiNote ? aiNote : "AI 自动补全(设置页「AI 补全」可配置)"
|
|
6086
|
+
}, react.default.createElement("span", { className: "edrv-lsp-status-dot " + (!aiEnabled ? "idle" : aiDotCls) }), react.default.createElement("span", { className: "edrv-sp-lsp" }, aiLabel)) : null;
|
|
6087
|
+
const statusBar = lspSeg || aiSeg ? react.default.createElement("div", { className: "edrv-statusbar" }, lspSeg, aiSeg) : null;
|
|
5677
6088
|
const navBackTarget = navRef.current.peekBack();
|
|
5678
6089
|
const navForwardTarget = navRef.current.peekForward();
|
|
5679
6090
|
const navNameOf = (entry) => entry && entry.path ? String(entry.path).split(/[\\/]/).pop() : "";
|
|
@@ -5952,7 +6363,7 @@ window.__ModuleLoader__.load({
|
|
|
5952
6363
|
flexDirection: "column",
|
|
5953
6364
|
overflow: "hidden"
|
|
5954
6365
|
}
|
|
5955
|
-
}, pathBar, tabRow, sideHintEl, editorArea,
|
|
6366
|
+
}, pathBar, tabRow, sideHintEl, editorArea, statusBar);
|
|
5956
6367
|
const editorRow = react.default.createElement("div", { className: "edrv-editor-row" }, sidebarPanels ? react.default.createElement(SidebarView, {
|
|
5957
6368
|
registry: sidebarPanels,
|
|
5958
6369
|
ctx: sidebarCtx,
|
|
@@ -6413,14 +6824,14 @@ window.__ModuleLoader__.load({
|
|
|
6413
6824
|
function registerSlotSafely(ctx, spec, render) {
|
|
6414
6825
|
const slots = ctx?.slots;
|
|
6415
6826
|
if (!slots || typeof slots.inject !== "function" || typeof slots.register !== "function") {
|
|
6416
|
-
|
|
6827
|
+
log.warn("slots 服务不可用,跳过 slot " + spec.name);
|
|
6417
6828
|
return null;
|
|
6418
6829
|
}
|
|
6419
6830
|
try {
|
|
6420
6831
|
const disposer = slots.inject(spec.name, () => slots.register(spec, render));
|
|
6421
6832
|
return typeof disposer === "function" ? disposer : null;
|
|
6422
6833
|
} catch (error) {
|
|
6423
|
-
|
|
6834
|
+
log.warn("slot " + spec.name + " 注册失败(" + String(error) + "),已跳过");
|
|
6424
6835
|
return null;
|
|
6425
6836
|
}
|
|
6426
6837
|
}
|
|
@@ -7861,6 +8272,111 @@ window.__ModuleLoader__.load({
|
|
|
7861
8272
|
}, busy === "add" ? "校验中…" : "添加")), react.default.createElement("small", null, "安装 = 复制到 <项目>/Packages/com.dsh.editor(内嵌包);更新 = 整目录替换;卸载 = 删除该目录。Unity 打开时切回窗口自动刷新生效。插件卸载/重载时会自动清理并恢复右键菜单注册(「移除注册」后不再恢复)。")));
|
|
7862
8273
|
}
|
|
7863
8274
|
//#endregion
|
|
8275
|
+
//#region src/client/ui/AiSettings.ts
|
|
8276
|
+
/**
|
|
8277
|
+
* dsh-vscode-mode client — AI 补全设置卡片。
|
|
8278
|
+
* 开关 + 模型下拉(edrv.ai.models 目录,按 provider 分组,默认「自动」)+
|
|
8279
|
+
* 思考强度下拉(随所选模型的 efforts 元数据动态渲染:无档位隐藏;含「跟随默认」)。
|
|
8280
|
+
* 保存走 edrv.ai.configUpdate;保存后广播 edrv:ai-config 事件让 provider 即时生效。
|
|
8281
|
+
* 作者 ddj
|
|
8282
|
+
*/
|
|
8283
|
+
/**
|
|
8284
|
+
* AI 补全设置卡片(设置页「AI 补全」Tab 主体)。
|
|
8285
|
+
* @author ddj
|
|
8286
|
+
*/
|
|
8287
|
+
function AiSettings() {
|
|
8288
|
+
const [cfg, setCfg] = react.default.useState(null);
|
|
8289
|
+
const [dir, setDir] = react.default.useState(null);
|
|
8290
|
+
const [note, setNote] = react.default.useState("");
|
|
8291
|
+
const [error, setError] = react.default.useState("");
|
|
8292
|
+
const [busy, setBusy] = react.default.useState(false);
|
|
8293
|
+
react.default.useEffect(() => {
|
|
8294
|
+
Promise.all([rpc("edrv.ai.configGet", {}), rpc("edrv.ai.models", {})]).then(([c, d]) => {
|
|
8295
|
+
if (c.ok) setCfg({
|
|
8296
|
+
enabled: c.enabled,
|
|
8297
|
+
provider: c.provider,
|
|
8298
|
+
model: c.model,
|
|
8299
|
+
effort: c.effort
|
|
8300
|
+
});
|
|
8301
|
+
else setError(c.error || "配置读取失败");
|
|
8302
|
+
if (d.ok) setDir(d);
|
|
8303
|
+
else setError(d.error || "模型目录读取失败");
|
|
8304
|
+
}).catch((e) => setError(String(e)));
|
|
8305
|
+
}, []);
|
|
8306
|
+
/** 所选 provider 下的模型条目(自动路由时取首组供档位参考)。 */
|
|
8307
|
+
const modelsOf = (provider) => {
|
|
8308
|
+
if (!dir?.providers?.length) return [];
|
|
8309
|
+
if (!provider) return dir.providers[0]?.models ?? [];
|
|
8310
|
+
return dir.providers.find((p) => p.id === provider)?.models ?? [];
|
|
8311
|
+
};
|
|
8312
|
+
/** 档位可选项:模型无 reasoning 元数据 → 空数组(选择器隐藏)。 */
|
|
8313
|
+
const efforts = (() => {
|
|
8314
|
+
const models = modelsOf(cfg?.provider);
|
|
8315
|
+
return models.find((m) => m.model === cfg?.model)?.efforts ?? models[0]?.efforts ?? [];
|
|
8316
|
+
})();
|
|
8317
|
+
const save = async (patch) => {
|
|
8318
|
+
setBusy(true);
|
|
8319
|
+
setError("");
|
|
8320
|
+
try {
|
|
8321
|
+
const res = await rpc("edrv.ai.configUpdate", patch);
|
|
8322
|
+
if (!res.ok) throw new Error(res.error || "保存失败");
|
|
8323
|
+
setCfg({
|
|
8324
|
+
enabled: res.enabled,
|
|
8325
|
+
provider: res.provider,
|
|
8326
|
+
model: res.model,
|
|
8327
|
+
effort: res.effort
|
|
8328
|
+
});
|
|
8329
|
+
setAiInlineEnabled(res.enabled);
|
|
8330
|
+
setNote("已保存");
|
|
8331
|
+
window.dispatchEvent(new CustomEvent("edrv:ai-config", { detail: { enabled: res.enabled } }));
|
|
8332
|
+
} catch (e) {
|
|
8333
|
+
setError(String(e));
|
|
8334
|
+
} finally {
|
|
8335
|
+
setBusy(false);
|
|
8336
|
+
}
|
|
8337
|
+
};
|
|
8338
|
+
const toggle = () => save({ enabled: !cfg.enabled });
|
|
8339
|
+
const pickModel = (value) => {
|
|
8340
|
+
if (!value) return save({
|
|
8341
|
+
provider: "",
|
|
8342
|
+
model: ""
|
|
8343
|
+
});
|
|
8344
|
+
const idx = value.indexOf("/");
|
|
8345
|
+
const provider = value.slice(0, idx);
|
|
8346
|
+
const model = value.slice(idx + 1);
|
|
8347
|
+
const keep = (modelsOf(provider).find((m) => m.model === model)?.efforts ?? []).some((e) => e.id === cfg.effort);
|
|
8348
|
+
save(keep ? {
|
|
8349
|
+
provider,
|
|
8350
|
+
model
|
|
8351
|
+
} : {
|
|
8352
|
+
provider,
|
|
8353
|
+
model,
|
|
8354
|
+
effort: ""
|
|
8355
|
+
});
|
|
8356
|
+
};
|
|
8357
|
+
if (!cfg) return react.default.createElement("div", { className: "vsm-mcp-empty" }, error || "正在读取 AI 补全配置…");
|
|
8358
|
+
const modelValue = cfg.provider && cfg.model ? cfg.provider + "/" + cfg.model : "";
|
|
8359
|
+
return react.default.createElement("section", { className: "vsm-lsp-card" }, react.default.createElement("h3", null, "AI 自动补全(实验)"), react.default.createElement("p", { className: "vsm-lsp-note" }, "编辑停顿后由模型生成内联建议(ghost text),Tab 接受,Alt+\\ 手动触发。每次补全是一次模型调用;建议选择非推理模型,且思考强度选「跟随默认」或最低档以获得更快响应。"), react.default.createElement("div", { className: "vsm-lsp-row" }, react.default.createElement("button", {
|
|
8360
|
+
className: cfg.enabled ? "vsm-primary" : "",
|
|
8361
|
+
disabled: busy,
|
|
8362
|
+
onClick: toggle
|
|
8363
|
+
}, cfg.enabled ? "已开启(点击关闭)" : "已关闭(点击开启)")), react.default.createElement("div", { className: "vsm-lsp-row" }, react.default.createElement("label", null, "模型", react.default.createElement("select", {
|
|
8364
|
+
value: modelValue,
|
|
8365
|
+
disabled: busy || !dir?.providers?.length,
|
|
8366
|
+
onChange: (e) => pickModel(e.target.value)
|
|
8367
|
+
}, react.default.createElement("option", { value: "" }, dir?.providers?.length ? "自动(取第一个可用模型)" : "无可用模型"), (dir?.providers ?? []).flatMap((p) => p.models.map((m) => react.default.createElement("option", {
|
|
8368
|
+
key: p.id + "/" + m.model,
|
|
8369
|
+
value: p.id + "/" + m.model
|
|
8370
|
+
}, p.name + " · " + m.name))))), efforts.length ? react.default.createElement("label", null, "思考强度", react.default.createElement("select", {
|
|
8371
|
+
value: cfg.effort,
|
|
8372
|
+
disabled: busy,
|
|
8373
|
+
onChange: (e) => save({ effort: e.target.value })
|
|
8374
|
+
}, react.default.createElement("option", { value: "" }, "跟随默认"), efforts.map((e) => react.default.createElement("option", {
|
|
8375
|
+
key: e.id,
|
|
8376
|
+
value: e.id
|
|
8377
|
+
}, e.name)))) : null), note && react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, note)), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error));
|
|
8378
|
+
}
|
|
8379
|
+
//#endregion
|
|
7864
8380
|
//#region src/client/ui/McpSettings.ts
|
|
7865
8381
|
/**
|
|
7866
8382
|
* dsh-vscode-mode client — VSCodeMode 设置区:通用 / 快捷键 / MCP 管理 / 语言服务器 / 性能优化 / 兼容性。
|
|
@@ -8372,6 +8888,7 @@ window.__ModuleLoader__.load({
|
|
|
8372
8888
|
});
|
|
8373
8889
|
else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
|
|
8374
8890
|
else if (tab === "lsp") body = react.default.createElement(LspSettings, null);
|
|
8891
|
+
else if (tab === "ai") body = react.default.createElement(AiSettings, null);
|
|
8375
8892
|
else body = react.default.createElement(PerfSettings, null);
|
|
8376
8893
|
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", {
|
|
8377
8894
|
className: tab === "general" ? "active" : "",
|
|
@@ -8386,6 +8903,9 @@ window.__ModuleLoader__.load({
|
|
|
8386
8903
|
className: tab === "lsp" ? "active" : "",
|
|
8387
8904
|
onClick: () => setTab("lsp")
|
|
8388
8905
|
}, "语言服务器"), react.default.createElement("button", {
|
|
8906
|
+
className: tab === "ai" ? "active" : "",
|
|
8907
|
+
onClick: () => setTab("ai")
|
|
8908
|
+
}, "AI 补全"), react.default.createElement("button", {
|
|
8389
8909
|
className: tab === "perf" ? "active" : "",
|
|
8390
8910
|
onClick: () => setTab("perf")
|
|
8391
8911
|
}, "性能优化"), react.default.createElement("button", {
|
|
@@ -8914,12 +9434,12 @@ window.__ModuleLoader__.load({
|
|
|
8914
9434
|
if (!params) return;
|
|
8915
9435
|
const referrer = options.referrer ?? (typeof document !== "undefined" ? document.referrer : "");
|
|
8916
9436
|
if (!referrerAllowed(referrer, options.origin ?? (typeof location !== "undefined" ? location.origin : ""))) {
|
|
8917
|
-
|
|
9437
|
+
log.warn("已忽略跨源深链请求(referrer=" + referrer + ")");
|
|
8918
9438
|
return;
|
|
8919
9439
|
}
|
|
8920
9440
|
stripCurrentUrl();
|
|
8921
9441
|
openDeepLink(ctx, params).catch((error) => {
|
|
8922
|
-
|
|
9442
|
+
log.warn("深链打开失败:" + String(error));
|
|
8923
9443
|
toastDom("深链打开失败:" + String(error?.message ?? error));
|
|
8924
9444
|
});
|
|
8925
9445
|
}
|
|
@@ -11776,8 +12296,8 @@ window.__ModuleLoader__.load({
|
|
|
11776
12296
|
let selected = autoValue("auto");
|
|
11777
12297
|
/** 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)是否已安装(compatSummary 展示用)。 */
|
|
11778
12298
|
let remoteOpenInstalled = false;
|
|
11779
|
-
/** 两条文件链接路由共用的路由日志(openPathRouter / remoteOpenRouter
|
|
11780
|
-
const routeLogger = (message) =>
|
|
12299
|
+
/** 两条文件链接路由共用的路由日志(openPathRouter / remoteOpenRouter;统一走插件日志器)。 */
|
|
12300
|
+
const routeLogger = (message) => log.warn(message);
|
|
11781
12301
|
/** 两条路由共用的 FileOpenContext:当前会话 id 与工作区 cwd。 */
|
|
11782
12302
|
const openContext = () => {
|
|
11783
12303
|
const current = sessions?.list?.getSnapshot?.();
|
|
@@ -11877,7 +12397,7 @@ window.__ModuleLoader__.load({
|
|
|
11877
12397
|
let remoteRetries = 0;
|
|
11878
12398
|
const retryRemoteOpen = () => {
|
|
11879
12399
|
if (remoteOpenInstalled || remoteRetries >= 15) {
|
|
11880
|
-
if (!remoteOpenInstalled)
|
|
12400
|
+
if (!remoteOpenInstalled) log.warn("未探测到 remote.session.openWorkspacePath,0.1.3+ 会话文件链接路由未安装");
|
|
11881
12401
|
return;
|
|
11882
12402
|
}
|
|
11883
12403
|
remoteRetries += 1;
|
|
@@ -11895,12 +12415,12 @@ window.__ModuleLoader__.load({
|
|
|
11895
12415
|
logger: routeLogger
|
|
11896
12416
|
});
|
|
11897
12417
|
if (!disposer) {
|
|
11898
|
-
|
|
12418
|
+
log.warn("remote.session 存在但 openWorkspacePath 不可补丁,会话文件链接路由未安装");
|
|
11899
12419
|
return;
|
|
11900
12420
|
}
|
|
11901
12421
|
remoteOpenInstalled = true;
|
|
11902
12422
|
ctx.effect(() => disposer, "vscode-mode: remote file link routing");
|
|
11903
|
-
|
|
12423
|
+
log.info("已安装 0.1.3+ 会话文件链接路由(remote.session.openWorkspacePath)");
|
|
11904
12424
|
}, 2e3);
|
|
11905
12425
|
};
|
|
11906
12426
|
retryRemoteOpen();
|
|
@@ -11971,7 +12491,7 @@ window.__ModuleLoader__.load({
|
|
|
11971
12491
|
if (sideService !== void 0) return;
|
|
11972
12492
|
sideService = detectSidebarService(ctx);
|
|
11973
12493
|
if (sideService !== void 0) {
|
|
11974
|
-
|
|
12494
|
+
log.info("检测到 dsh-better-sidebar,切换侧边栏编辑形态");
|
|
11975
12495
|
applySideForm();
|
|
11976
12496
|
} else retrySideService();
|
|
11977
12497
|
}, 2e3);
|