zcode-pro 0.20.3 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +9 -6
- package/README.md +8 -5
- package/dist/inject.js +2658 -1318
- package/package.json +1 -1
- package/src/host/helper.mjs +156 -60
- package/src/host/main.mjs +7 -4
- package/src/host/taskIndex.mjs +97 -0
- package/src/host/version.mjs +51 -0
- package/src/inject/core.js +177 -24
- package/src/inject/features/alias-migration.js +470 -0
- package/src/inject/features/alias.js +6 -1
- package/src/inject/features/idle-reclaim.js +23 -48
- package/src/inject/features/pinned-collapse.js +4 -3
- package/src/inject/features/project-menu.js +209 -19
- package/src/inject/features/sandbox-reclaim.js +121 -0
- package/src/inject/features/session-switch.js +164 -25
- package/src/inject/features/settings-dialog.js +225 -61
- package/src/inject/features/styles.js +8 -1
- package/src/inject/features/title-lock.js +20 -2
- package/src/inject/features/toolbar-icons.js +85 -45
- package/src/inject/features/ws-rows.js +62 -0
- package/src/inject/features/ws-running.js +391 -27
- package/src/inject/index.js +3 -2
- package/src/inject/ui.js +34 -11
- package/src/inject/features/pinned-expand.js +0 -142
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// “ZCode Pro 增强设置”弹窗:功能开关 + 样式调整 + 运行状态。
|
|
2
2
|
// 顶部标签页切换(视觉参考侧栏“分组/项目”切换);配置写入 helper(~/.zcode/zcodepro.json)。
|
|
3
|
-
import { h, t, rpc, getConfig, clearConfigCache, errText, HELPER_URL, isZhLocale } from '../core.js';
|
|
3
|
+
import { h, t, rpc, getConfig, clearConfigCache, errText, HELPER_URL, isZhLocale, isNewProjectSidebar, DEFAULT_SHORTCUTS } from '../core.js';
|
|
4
4
|
import { openDialog, dialogFooter, btnPrimary, btnSecondary, btnSmall, settingRow, ensureStyle, showToast, numberField, unitField } from '../ui.js';
|
|
5
5
|
import { comboField, providerOptionsOf, modelOptionsOf, modelPlaceholderOf, isManualConn } from '../provider-fields.js';
|
|
6
6
|
import { refreshAliases } from './alias.js';
|
|
7
|
+
import { migrationCard } from './alias-migration.js';
|
|
7
8
|
import { applyStyles, STYLE_DEFAULTS, showStyleHighlight, hideStyleHighlight } from './styles.js';
|
|
8
9
|
import { copyToClipboard, hCopyIcon } from './copy-path.js';
|
|
9
10
|
|
|
@@ -12,6 +13,9 @@ function openExternal(url) {
|
|
|
12
13
|
try { void window.zcode.openExternal(url); } catch { /* ignore */ }
|
|
13
14
|
}
|
|
14
15
|
|
|
16
|
+
// 插件市场仓库(设置页“自动更新”行的仓库名链接指向它)
|
|
17
|
+
const PLUGINS_REPO_URL = 'https://github.com/duanluan/zcode-plugins';
|
|
18
|
+
|
|
15
19
|
// 本体升级交互(Headroom 的 pip 与 rtk 的 GitHub Releases 共用):
|
|
16
20
|
// 检查更新 → 升级(helper 后台任务,轮询进度,输出取最后一行、超长省略中间)→
|
|
17
21
|
// 进行中可停止;关弹窗不中断升级,重开恢复显示。endpoint 为 /headroom 或 /rtk,
|
|
@@ -236,10 +240,85 @@ export function openSettingsDialog() {
|
|
|
236
240
|
);
|
|
237
241
|
|
|
238
242
|
const rows = h('div', { class: 'divide-y divide-border rounded-xl border border-border' });
|
|
243
|
+
const pluginsStatusLine = h('span', { class: 'max-w-40 truncate text-ui-xs/relaxed text-foreground-subtle' }, '…');
|
|
244
|
+
const pluginsBtn = btnSmall(L.pluginsCheckNow, () => { void runPluginsUpdate(); });
|
|
245
|
+
let pluginsUpdating = false;
|
|
246
|
+
const runPluginsUpdate = async () => {
|
|
247
|
+
// 并发守卫:打开弹窗的自动触发与手动点击可能撞在一起,第二个直接忽略
|
|
248
|
+
if (pluginsUpdating) return;
|
|
249
|
+
pluginsUpdating = true;
|
|
250
|
+
pluginsBtn.disabled = true;
|
|
251
|
+
pluginsStatusLine.textContent = L.pluginsUpdating;
|
|
252
|
+
const res = await rpc('/plugins/update', { method: 'POST', body: {} });
|
|
253
|
+
pluginsUpdating = false;
|
|
254
|
+
pluginsBtn.disabled = false;
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
pluginsStatusLine.textContent = '';
|
|
257
|
+
showToast(L.pluginsUpdateFailed + ': ' + errText(res), 'error');
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
pluginsStatusLine.textContent = res.updated > 0
|
|
261
|
+
? L.pluginsUpdatedDone.replaceAll('{n}', String(res.updated))
|
|
262
|
+
: L.pluginsUpToDate;
|
|
263
|
+
showToast(pluginsStatusLine.textContent, 'success');
|
|
264
|
+
};
|
|
265
|
+
void (async () => {
|
|
266
|
+
// 打开设置弹窗即自动更新(与启动时同语义):开关开着且有新版时直接更,
|
|
267
|
+
// 状态行如实显示过程与结果;开关关闭时只检查并显示“N 个可更新”
|
|
268
|
+
const cfg = await getConfig(true).catch(() => null);
|
|
269
|
+
const res = await rpc('/plugins/status');
|
|
270
|
+
if (!res.ok) { pluginsStatusLine.textContent = ''; return; }
|
|
271
|
+
const n = (res.updates || []).length;
|
|
272
|
+
if (cfg && (cfg.features || {}).autoUpdatePlugins === true && n > 0) {
|
|
273
|
+
void runPluginsUpdate();
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
pluginsStatusLine.textContent = n > 0
|
|
277
|
+
? L.pluginsUpdatesFound.replaceAll('{n}', String(n))
|
|
278
|
+
: L.pluginsUpToDate;
|
|
279
|
+
})();
|
|
280
|
+
|
|
281
|
+
// 会话快捷切换:入口键位(“快捷键”标签页可改);此处定义在 refreshRows 之前,
|
|
282
|
+
// 功能行的描述与快捷键面板共用同一份当前键位
|
|
283
|
+
const HK_ACTIONS = [
|
|
284
|
+
['switchLast', 'hkAltZ'],
|
|
285
|
+
['switchPrev', 'hkAltX'],
|
|
286
|
+
['switchNext', 'hkAltC'],
|
|
287
|
+
['switchRecent', 'hkAltE'],
|
|
288
|
+
['switchAttention', 'hkAltR'],
|
|
289
|
+
];
|
|
290
|
+
const hkNames = () => ({
|
|
291
|
+
switchLast: L.hkNameLast, switchPrev: L.hkNamePrev, switchNext: L.hkNameNext,
|
|
292
|
+
switchRecent: L.hkNameRecent, switchAttention: L.hkNameAttention,
|
|
293
|
+
});
|
|
294
|
+
// 当前生效键位:配置里合法的单字符覆盖默认,其余回退默认
|
|
295
|
+
const hkKeys = () => {
|
|
296
|
+
const sc = config.shortcuts || {};
|
|
297
|
+
const keys = {};
|
|
298
|
+
for (const [a] of HK_ACTIONS) {
|
|
299
|
+
keys[a] = /^[a-z0-9]$/.test(sc[a]) ? sc[a] : DEFAULT_SHORTCUTS[a];
|
|
300
|
+
}
|
|
301
|
+
return keys;
|
|
302
|
+
};
|
|
303
|
+
// 开关描述按当前键位渲染(改键后 refreshRows 重绘即更新);
|
|
304
|
+
// prev/next 出现在“按住 alt 再按 {prev}/{next}”语境,用裸键位避免 alt 重复
|
|
305
|
+
const sessionSwitchDesc = () => {
|
|
306
|
+
const k = hkKeys();
|
|
307
|
+
return L.featureSessionSwitchDesc
|
|
308
|
+
.replaceAll('{last}', 'alt+' + k.switchLast)
|
|
309
|
+
.replaceAll('{prev}', k.switchPrev)
|
|
310
|
+
.replaceAll('{next}', k.switchNext)
|
|
311
|
+
.replaceAll('{recent}', 'alt+' + k.switchRecent)
|
|
312
|
+
.replaceAll('{attention}', 'alt+' + k.switchAttention);
|
|
313
|
+
};
|
|
314
|
+
|
|
239
315
|
const refreshRows = () => {
|
|
240
316
|
const f = config.features || {};
|
|
241
317
|
rows.replaceChildren(
|
|
242
|
-
|
|
318
|
+
// 3.16.1 起官方“编辑项目”支持修改名称:别名停用,此行只描述其余三项;
|
|
319
|
+
// 仍有存量别名时在最上方放迁移卡片(卡片自管移除,这里每次重建)
|
|
320
|
+
...(isNewProjectSidebar() ? [migrationCard()] : []),
|
|
321
|
+
settingRow(L.featureProjectMenu, isNewProjectSidebar() ? L.featureProjectMenuDescNew : L.featureProjectMenuDesc, f.projectMenu !== false, async () => {
|
|
243
322
|
const next = !(f.projectMenu !== false);
|
|
244
323
|
if (await setFeature('projectMenu', next)) f.projectMenu = next;
|
|
245
324
|
refreshRows();
|
|
@@ -251,7 +330,7 @@ export function openSettingsDialog() {
|
|
|
251
330
|
if (await setFeature('taskOrder', next)) f.taskOrder = next;
|
|
252
331
|
refreshRows();
|
|
253
332
|
}),
|
|
254
|
-
settingRow(L.featureSessionSwitch,
|
|
333
|
+
settingRow(L.featureSessionSwitch, sessionSwitchDesc(), f.sessionSwitch !== false, async () => {
|
|
255
334
|
const next = !(f.sessionSwitch !== false);
|
|
256
335
|
if (await setFeature('sessionSwitch', next)) f.sessionSwitch = next;
|
|
257
336
|
refreshRows();
|
|
@@ -281,16 +360,31 @@ export function openSettingsDialog() {
|
|
|
281
360
|
if (await setFeature('wsRunningSpin', next)) f.wsRunningSpin = next;
|
|
282
361
|
refreshRows();
|
|
283
362
|
}),
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
363
|
+
(() => {
|
|
364
|
+
// 标题 = “自动更新 duanluan/zcode-plugins 插件”,仓库名可点击跳转仓库
|
|
365
|
+
const repoLink = h('span', {
|
|
366
|
+
class: 'cursor-pointer underline-offset-4 hover:underline',
|
|
367
|
+
title: PLUGINS_REPO_URL,
|
|
368
|
+
onClick: (ev) => { ev.stopPropagation(); openExternal(PLUGINS_REPO_URL); },
|
|
369
|
+
}, L.featureAutoUpdatePluginsRepo);
|
|
370
|
+
const row = settingRow('', '', f.autoUpdatePlugins === true, async () => {
|
|
371
|
+
const next = !(f.autoUpdatePlugins === true);
|
|
372
|
+
if (await setFeature('autoUpdatePlugins', next)) f.autoUpdatePlugins = next;
|
|
373
|
+
refreshRows();
|
|
374
|
+
}, h('div', { class: 'flex items-center gap-2' }, pluginsBtn));
|
|
375
|
+
// 标题行:自动更新 <链接> 插件;描述位放状态字(检查结果/更新进度)
|
|
376
|
+
const nameEl = row.querySelector('.text-ui-sm.font-medium');
|
|
377
|
+
if (nameEl) {
|
|
378
|
+
nameEl.replaceChildren(
|
|
379
|
+
document.createTextNode(L.featureAutoUpdatePlugins + ' '),
|
|
380
|
+
repoLink,
|
|
381
|
+
document.createTextNode(' 插件'),
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
const descEl = row.querySelector('[data-zcodepro-row-desc]');
|
|
385
|
+
if (descEl) descEl.replaceChildren(pluginsStatusLine);
|
|
386
|
+
return row;
|
|
387
|
+
})(),
|
|
294
388
|
);
|
|
295
389
|
};
|
|
296
390
|
refreshRows();
|
|
@@ -333,56 +427,108 @@ export function openSettingsDialog() {
|
|
|
333
427
|
let activeTab = 'features';
|
|
334
428
|
// 插件更新卡片:显示 duanluan-zcode-plugins 市场状态,一键更新(helper /plugins/* 端点);
|
|
335
429
|
// 状态异步加载,打开弹窗不等待
|
|
336
|
-
|
|
337
|
-
const pluginsBtn = btnSmall(L.pluginsCheckNow, () => { void runPluginsUpdate(); });
|
|
338
|
-
const runPluginsUpdate = async () => {
|
|
339
|
-
pluginsBtn.disabled = true;
|
|
340
|
-
pluginsStatusLine.textContent = L.pluginsUpdating;
|
|
341
|
-
const res = await rpc('/plugins/update', { method: 'POST', body: {} });
|
|
342
|
-
pluginsBtn.disabled = false;
|
|
343
|
-
if (!res.ok) {
|
|
344
|
-
pluginsStatusLine.textContent = '';
|
|
345
|
-
showToast(L.pluginsUpdateFailed + ': ' + errText(res), 'error');
|
|
346
|
-
return;
|
|
347
|
-
}
|
|
348
|
-
pluginsStatusLine.textContent = res.updated > 0
|
|
349
|
-
? L.pluginsUpdatedDone.replaceAll('{n}', String(res.updated))
|
|
350
|
-
: L.pluginsUpToDate;
|
|
351
|
-
showToast(pluginsStatusLine.textContent, 'success');
|
|
352
|
-
};
|
|
353
|
-
void (async () => {
|
|
354
|
-
const res = await rpc('/plugins/status');
|
|
355
|
-
if (!res.ok) { pluginsStatusLine.textContent = ''; return; }
|
|
356
|
-
pluginsStatusLine.textContent = (res.updates || []).length > 0
|
|
357
|
-
? L.pluginsUpdatesFound.replaceAll('{n}', String(res.updates.length))
|
|
358
|
-
: L.pluginsUpToDate;
|
|
359
|
-
})();
|
|
360
|
-
// 标题中的 zcode-plugins 可点击跳转插件市场仓库(依赖宿主 openExternal 打开系统浏览器)
|
|
361
|
-
const PLUGINS_REPO_URL = 'https://github.com/duanluan/zcode-plugins';
|
|
362
|
-
const repoName = 'zcode-plugins';
|
|
363
|
-
const titleParts = L.pluginsUpdateTitle.split(repoName);
|
|
364
|
-
const pluginsTitle = h('div', { class: 'truncate text-ui-sm font-medium text-foreground' });
|
|
365
|
-
if (titleParts.length === 2 && typeof window.zcode?.openExternal === 'function') {
|
|
366
|
-
pluginsTitle.append(titleParts[0],
|
|
367
|
-
h('span', {
|
|
368
|
-
class: 'cursor-pointer underline-offset-4 hover:underline',
|
|
369
|
-
title: PLUGINS_REPO_URL,
|
|
370
|
-
onClick: () => openExternal(PLUGINS_REPO_URL),
|
|
371
|
-
}, repoName),
|
|
372
|
-
titleParts[1]);
|
|
373
|
-
} else {
|
|
374
|
-
pluginsTitle.append(L.pluginsUpdateTitle);
|
|
375
|
-
}
|
|
376
|
-
const pluginsCard = h('div', { class: 'mt-3 flex items-center justify-between gap-2 rounded-xl border border-border p-2' },
|
|
377
|
-
h('div', { class: 'min-w-0' },
|
|
378
|
-
pluginsTitle,
|
|
379
|
-
h('div', { class: 'mt-0.5 truncate text-ui-xs/relaxed text-foreground-subtle' }, pluginsStatusLine)),
|
|
380
|
-
pluginsBtn);
|
|
430
|
+
|
|
381
431
|
const paneFeatures = h('div', { role: 'tabpanel', class: 'mt-4' },
|
|
382
432
|
h('div', {}, rows),
|
|
383
|
-
pluginsCard,
|
|
384
433
|
communityCards,
|
|
385
434
|
);
|
|
435
|
+
// “快捷键”:五个入口键可改(点“修改”后按下新键,字母/数字,Esc 取消),
|
|
436
|
+
// 弹窗内通用键(↑/↓、Enter、Esc、松开 alt)固定只读;开关仍在“功能”标签页。
|
|
437
|
+
// 键位与名称定义(HK_ACTIONS/hkNames/hkKeys)在 refreshRows 前共用
|
|
438
|
+
let hkRecording = null; // 正在录制新键的动作(null = 未在录制)
|
|
439
|
+
let hkKeyHandler = null;
|
|
440
|
+
const hkCard = h('div', { class: 'divide-y divide-border rounded-xl border border-border' });
|
|
441
|
+
const hkCell = (keyLabel, desc, extra) => h('div', { class: 'flex items-start gap-3 px-3 py-2.5' },
|
|
442
|
+
h('kbd', {
|
|
443
|
+
class: 'shrink-0 min-w-20 rounded-md border border-border px-2 py-0.5 text-center text-ui-xs text-foreground-subtle',
|
|
444
|
+
style: 'font-family: ui-monospace, SFMono-Regular, Menlo, monospace; line-height: 1.5;',
|
|
445
|
+
}, keyLabel),
|
|
446
|
+
h('div', { class: 'min-w-0 flex-1 text-ui-sm text-foreground-subtle' }, desc),
|
|
447
|
+
extra ? h('div', { class: 'ml-auto shrink-0 pl-3' }, extra) : null);
|
|
448
|
+
const renderShortcuts = () => {
|
|
449
|
+
const keys = hkKeys();
|
|
450
|
+
const names = hkNames();
|
|
451
|
+
hkCard.replaceChildren(
|
|
452
|
+
...HK_ACTIONS.map(([a, descKey]) => {
|
|
453
|
+
const recording = hkRecording === a;
|
|
454
|
+
return hkCell(
|
|
455
|
+
recording ? L.hkRecording : 'alt + ' + keys[a],
|
|
456
|
+
L[descKey],
|
|
457
|
+
btnSmall(recording ? L.hkRecording : L.hkModify, () => { recording ? stopHkRecording() : startHkRecording(a); }));
|
|
458
|
+
}),
|
|
459
|
+
hkCell('↑ / ↓', L.hkArrow.replaceAll('{prev}', keys.switchPrev).replaceAll('{next}', keys.switchNext)),
|
|
460
|
+
hkCell('Enter', L.hkEnter),
|
|
461
|
+
hkCell('Esc', L.hkEsc),
|
|
462
|
+
hkCell(L.hkReleaseKey, L.hkRelease));
|
|
463
|
+
};
|
|
464
|
+
const stopHkRecording = () => {
|
|
465
|
+
hkRecording = null;
|
|
466
|
+
if (hkKeyHandler) {
|
|
467
|
+
document.removeEventListener('keydown', hkKeyHandler, true);
|
|
468
|
+
hkKeyHandler = null;
|
|
469
|
+
}
|
|
470
|
+
renderShortcuts();
|
|
471
|
+
};
|
|
472
|
+
const startHkRecording = (action) => {
|
|
473
|
+
hkRecording = action;
|
|
474
|
+
if (!hkKeyHandler) {
|
|
475
|
+
// 绑定本次弹窗实例:弹窗关闭(哪怕马上重开新弹窗)后旧监听器自摘,
|
|
476
|
+
// 不会吞掉新弹窗里的按键
|
|
477
|
+
const myOverlay = document.querySelector('.zcodepro-overlay');
|
|
478
|
+
hkKeyHandler = (e) => {
|
|
479
|
+
if (!myOverlay || !myOverlay.isConnected) { stopHkRecording(); return; }
|
|
480
|
+
if (e.isComposing) return;
|
|
481
|
+
e.preventDefault();
|
|
482
|
+
e.stopImmediatePropagation();
|
|
483
|
+
if (e.key === 'Escape') { stopHkRecording(); return; }
|
|
484
|
+
const k = e.key.toLowerCase();
|
|
485
|
+
if (!/^[a-z0-9]$/.test(k)) { showToast(L.hkInvalid, 'error'); return; }
|
|
486
|
+
const keys = hkKeys();
|
|
487
|
+
const names = hkNames();
|
|
488
|
+
const conflict = HK_ACTIONS.find(([a]) => a !== hkRecording && keys[a] === k);
|
|
489
|
+
if (conflict) {
|
|
490
|
+
showToast(L.hkConflict.replaceAll('{k}', k).replaceAll('{name}', names[conflict[0]]), 'error');
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
const a = hkRecording;
|
|
494
|
+
stopHkRecording();
|
|
495
|
+
void (async () => {
|
|
496
|
+
const res = await rpc('/config', { method: 'POST', body: { shortcuts: { [a]: k } } });
|
|
497
|
+
clearConfigCache();
|
|
498
|
+
window.dispatchEvent(new CustomEvent('zcodepro:config-changed'));
|
|
499
|
+
if (!res.ok) { showToast(L.failed + ': ' + errText(res), 'error'); renderShortcuts(); return; }
|
|
500
|
+
if (res.config && res.config.shortcuts) config.shortcuts = res.config.shortcuts;
|
|
501
|
+
showToast(L.hkSaved, 'success');
|
|
502
|
+
refreshRows(); // 功能标签页的开关描述跟随新键位
|
|
503
|
+
renderShortcuts();
|
|
504
|
+
})();
|
|
505
|
+
};
|
|
506
|
+
document.addEventListener('keydown', hkKeyHandler, true);
|
|
507
|
+
}
|
|
508
|
+
renderShortcuts();
|
|
509
|
+
};
|
|
510
|
+
renderShortcuts();
|
|
511
|
+
// 重置为默认:五个入口键一次性写回默认值,保存路径与单个改键一致
|
|
512
|
+
const resetShortcuts = async () => {
|
|
513
|
+
stopHkRecording(); // 录制进行中点重置:先结束录制再写回默认
|
|
514
|
+
const all = {};
|
|
515
|
+
for (const [a] of HK_ACTIONS) all[a] = DEFAULT_SHORTCUTS[a];
|
|
516
|
+
const res = await rpc('/config', { method: 'POST', body: { shortcuts: all } });
|
|
517
|
+
clearConfigCache();
|
|
518
|
+
window.dispatchEvent(new CustomEvent('zcodepro:config-changed'));
|
|
519
|
+
if (!res.ok) { showToast(L.failed + ': ' + errText(res), 'error'); return; }
|
|
520
|
+
if (res.config && res.config.shortcuts) config.shortcuts = res.config.shortcuts;
|
|
521
|
+
showToast(L.hkResetDone, 'success');
|
|
522
|
+
refreshRows(); // 功能标签页的开关描述跟随默认键位
|
|
523
|
+
renderShortcuts();
|
|
524
|
+
};
|
|
525
|
+
const paneShortcuts = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' },
|
|
526
|
+
h('div', { class: 'mb-2 text-ui-sm font-medium' }, L.hkTitle),
|
|
527
|
+
h('div', { class: 'mb-2 flex items-center gap-2' },
|
|
528
|
+
h('div', { class: 'min-w-0 flex-1 text-ui-xs/relaxed text-foreground-subtle' }, L.hkEditHint),
|
|
529
|
+
h('div', { class: 'shrink-0' }, btnSmall(L.hkReset, () => { void resetShortcuts(); }))),
|
|
530
|
+
hkCard,
|
|
531
|
+
);
|
|
386
532
|
const paneStyles = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
|
|
387
533
|
const panePerf = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
|
|
388
534
|
const paneAgents = h('div', { role: 'tabpanel', class: 'mt-2', style: 'display:none' });
|
|
@@ -393,9 +539,10 @@ export function openSettingsDialog() {
|
|
|
393
539
|
const paneHeadroom = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
|
|
394
540
|
const paneTitleGen = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
|
|
395
541
|
const paneDebug = h('div', { role: 'tabpanel', class: 'mt-4', style: 'display:none' });
|
|
396
|
-
const panes = { features: paneFeatures, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
|
|
542
|
+
const panes = { features: paneFeatures, shortcuts: paneShortcuts, styles: paneStyles, perf: panePerf, agents: paneAgents, proxy: paneProxy, sync: paneSync, vision: paneVision, headroom: paneHeadroom, rtk: paneRtk, debug: paneDebug };
|
|
397
543
|
const tabDefs = [
|
|
398
544
|
['features', L.tabFeatures],
|
|
545
|
+
['shortcuts', L.tabShortcuts],
|
|
399
546
|
['agents', L.tabBehavior],
|
|
400
547
|
['styles', L.tabStyles],
|
|
401
548
|
['perf', L.tabPerformance],
|
|
@@ -418,6 +565,8 @@ export function openSettingsDialog() {
|
|
|
418
565
|
}, label)));
|
|
419
566
|
const switchTab = (name) => {
|
|
420
567
|
activeTab = name;
|
|
568
|
+
// 切走“快捷键”标签页时结束键位录制:否则在其它标签页打字会被录制监听吞掉
|
|
569
|
+
if (name !== 'shortcuts' && hkRecording) stopHkRecording();
|
|
421
570
|
for (const [id, pane] of Object.entries(panes)) pane.style.display = id === name ? '' : 'none';
|
|
422
571
|
renderTabs();
|
|
423
572
|
};
|
|
@@ -497,6 +646,7 @@ export function openSettingsDialog() {
|
|
|
497
646
|
cellPadH.el, h('span', { class: 'w-3 text-ui-xs text-foreground-subtle' }, 'px'))),
|
|
498
647
|
};
|
|
499
648
|
const cells = [
|
|
649
|
+
// 侧栏间距双结构:3.15.x 旧容器/行 + 3.16.1 新项目行(virtual-project-row)与任务行
|
|
500
650
|
styleCell(L.sidebarProjectSpacingName, L.sidebarProjectSpacingDesc, 'sidebarProjectSpacing', { max: 24 }),
|
|
501
651
|
styleCell(L.sidebarTaskSpacingName, L.sidebarTaskSpacingDesc, 'sidebarTaskSpacing', { max: 24 }),
|
|
502
652
|
widthCell,
|
|
@@ -1853,6 +2003,7 @@ export function openSettingsDialog() {
|
|
|
1853
2003
|
const hrStatusLine = h('div', { class: 'flex items-center gap-2' },
|
|
1854
2004
|
h('span', { class: 'text-ui-sm text-foreground-subtle/70' }, '…'));
|
|
1855
2005
|
const hrFacts = h('div', { class: 'mt-1.5 grid grid-cols-2 gap-x-4 gap-y-1 sm:grid-cols-3' });
|
|
2006
|
+
const hrInstList = h('div', { class: 'mt-1.5 space-y-1', style: 'display:none' });
|
|
1856
2007
|
const hrHintLine = h('p', { class: 'mt-1.5 text-ui-xs/relaxed text-amber-500', style: 'display:none' });
|
|
1857
2008
|
const hrRefresh = async () => {
|
|
1858
2009
|
const res = await rpc('/headroom/status');
|
|
@@ -1860,6 +2011,8 @@ export function openSettingsDialog() {
|
|
|
1860
2011
|
hrStatusLine.replaceChildren(
|
|
1861
2012
|
h('span', { class: 'text-ui-sm text-destructive' }, L.headroomStatusLoadFailed + ': ' + errText(res)));
|
|
1862
2013
|
hrFacts.replaceChildren();
|
|
2014
|
+
hrInstList.replaceChildren();
|
|
2015
|
+
hrInstList.style.display = 'none';
|
|
1863
2016
|
hrHintLine.style.display = 'none';
|
|
1864
2017
|
return;
|
|
1865
2018
|
}
|
|
@@ -1876,9 +2029,18 @@ export function openSettingsDialog() {
|
|
|
1876
2029
|
fact(L.headroomCurrentBackend, hrDevLabel(s.backend)),
|
|
1877
2030
|
fact(L.headroomDesiredBackend, hrDevLabel(s.desiredBackend)),
|
|
1878
2031
|
fact(L.headroomPowerState, HR_PM[s.powerMode] || s.powerMode),
|
|
1879
|
-
fact(L.headroomWatcher, s.watcherRunning ? L.headroomWatcherOn : L.headroomWatcherOff),
|
|
2032
|
+
fact(L.headroomWatcher, s.watcherRunning == null ? '—' : (s.watcherRunning ? L.headroomWatcherOn : L.headroomWatcherOff)),
|
|
1880
2033
|
fact(L.headroomSaverDetect, HR_DETECT[s.saverDetect] || s.saverDetect),
|
|
1881
2034
|
);
|
|
2035
|
+
// 多实例模式:列出各压缩实例(端口/格式/上游/供应商)与运行状态
|
|
2036
|
+
const insts = Array.isArray(s.instances) ? s.instances : [];
|
|
2037
|
+
hrInstList.replaceChildren(...insts.map((it) => h('div', { class: 'flex min-w-0 items-center gap-2' },
|
|
2038
|
+
h('span', { class: 'shrink-0 font-mono text-ui-xs text-foreground-subtle' }, `:${it.port}`),
|
|
2039
|
+
h('span', { class: 'min-w-0 flex-1 truncate text-ui-xs text-foreground-subtle', title: `${it.upstream} | ${it.providers}` },
|
|
2040
|
+
`${it.format} → ${it.upstream}`),
|
|
2041
|
+
h('span', { class: 'max-w-[40%] shrink-0 truncate text-ui-xs text-foreground-subtle/70', title: it.providers }, it.providers),
|
|
2042
|
+
h('span', { class: 'shrink-0 text-ui-xs ' + (it.up ? 'text-emerald-600' : 'text-destructive') }, it.up ? L.hrInstRunning : L.hrInstStopped))));
|
|
2043
|
+
hrInstList.style.display = insts.length ? '' : 'none';
|
|
1882
2044
|
hrHintLine.textContent = s.saverHint || '';
|
|
1883
2045
|
hrHintLine.style.display = s.saverHint ? '' : 'none';
|
|
1884
2046
|
};
|
|
@@ -2052,6 +2214,7 @@ export function openSettingsDialog() {
|
|
|
2052
2214
|
h('div', { class: 'flex items-center gap-1.5' }, hrRefreshBtn, hrStartBtn, hrRestartBtn, hrStopBtn)),
|
|
2053
2215
|
hrStatusLine,
|
|
2054
2216
|
hrFacts,
|
|
2217
|
+
hrInstList,
|
|
2055
2218
|
hrHintLine),
|
|
2056
2219
|
h('div', {
|
|
2057
2220
|
class: 'mt-2 rounded-xl border border-border p-1',
|
|
@@ -2227,6 +2390,7 @@ export function openSettingsDialog() {
|
|
|
2227
2390
|
statusLine,
|
|
2228
2391
|
tablist,
|
|
2229
2392
|
paneFeatures,
|
|
2393
|
+
paneShortcuts,
|
|
2230
2394
|
paneAgents,
|
|
2231
2395
|
paneStyles,
|
|
2232
2396
|
panePerf,
|
|
@@ -214,6 +214,9 @@ function buildCss(styles) {
|
|
|
214
214
|
`${LIST} [data-testid^="workspace-item-"] > div:first-child{align-self:stretch !important;}`,
|
|
215
215
|
`${SCROLL} [data-slot="collapsible-trigger"],${SCROLL} .flex.h-7{height:auto !important;}`,
|
|
216
216
|
`${SCROLL} [data-slot="collapsible-trigger"]{min-height:${28 + 2 * pad}px !important;padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
|
|
217
|
+
// 3.16.1 新结构:项目行(virtual-project-row,固定 h-8)按视觉总量放行内留白;
|
|
218
|
+
// 分区间距由新侧栏容器自己的布局管理,不动
|
|
219
|
+
`section[data-project-id] [data-testid="virtual-project-row"]{height:auto !important;min-height:${28 + 2 * pad}px !important;padding-block:${Math.round(pad)}px !important;}`,
|
|
217
220
|
);
|
|
218
221
|
}
|
|
219
222
|
const sts = styles.sidebarTaskSpacing;
|
|
@@ -228,6 +231,9 @@ function buildCss(styles) {
|
|
|
228
231
|
`ul.space-y-0\\.5:has(> li[data-task-item-key]) > li{padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
|
|
229
232
|
`${SCROLL} .flex.flex-col.gap-2{gap:${m}px !important;row-gap:${m}px !important;}`,
|
|
230
233
|
`${SCROLL} div.cursor-pointer[class*="pl-8"]{padding-block:${pad}px !important;padding-top:${pad}px !important;padding-bottom:${pad}px !important;}`,
|
|
234
|
+
// 3.16.1 新结构:任务行(li[data-task-item-key])行内留白 + 相邻行间距
|
|
235
|
+
`li[data-task-item-key]{padding-block:${Math.max(1, Math.round(pad))}px !important;}`,
|
|
236
|
+
`li[data-task-item-key] + li[data-task-item-key]{margin-block-start:${m}px !important;}`,
|
|
231
237
|
);
|
|
232
238
|
}
|
|
233
239
|
return parts.join('');
|
|
@@ -275,7 +281,8 @@ const HL_SELECTORS = {
|
|
|
275
281
|
thinkingLineHeight: `[data-testid^="chat-assistant-history-content"]`,
|
|
276
282
|
userLineHeight: `${CONV} [class*="user-row"] .whitespace-pre-wrap`,
|
|
277
283
|
contentWidth: '[data-v4-timeline-content-column]',
|
|
278
|
-
|
|
284
|
+
// 双结构:新侧栏项目行 / 旧 testid 行
|
|
285
|
+
sidebarProjectSpacing: 'section[data-project-id] [data-testid="virtual-project-row"], [data-testid^="workspace-item-"]',
|
|
279
286
|
sidebarTaskSpacing: 'li[data-task-item-key]',
|
|
280
287
|
};
|
|
281
288
|
|
|
@@ -28,6 +28,7 @@ const REVERIFY_MS = 3000; // 回写后的再复核:纠正数据库写
|
|
|
28
28
|
const MAP_REFRESH_MS = 60000; // 任务行变动后至多一次的延迟表刷新:让新重命名会话进入锁定范围
|
|
29
29
|
|
|
30
30
|
let installed = false;
|
|
31
|
+
const seenRows = new WeakSet(); // 已见过的任务行;新上屏的行立即钉回(不等复核)
|
|
31
32
|
let enabled = true; // titleLock 开关(/pinned-titles 响应带来)
|
|
32
33
|
let pinned = null; // Map(task_id → 重命名标题);null = 尚未取到/功能关闭
|
|
33
34
|
let resolved = false; // 首次成功通信(无论开关状态)后不再重试启动拉取
|
|
@@ -100,7 +101,19 @@ function writeTitle(el, text) {
|
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
// 失配 → 排期复核;到期后行可能已被 React 重挂载,按键重新定位
|
|
103
|
-
function scheduleEnforce(key) {
|
|
104
|
+
function scheduleEnforce(key, { immediate = false } = {}) {
|
|
105
|
+
if (immediate) {
|
|
106
|
+
// 新上屏的行(项目展开/滚动复现)带着旧名出现,用户此刻正看着列表——
|
|
107
|
+
// 立即用缓存表钉回,不等复核(编辑中的行不会到这:isMismatch 已排除 input)
|
|
108
|
+
if (enabled && pinned) {
|
|
109
|
+
const want = pinned.get(sessIdOf(key));
|
|
110
|
+
if (want !== undefined) {
|
|
111
|
+
for (const item of findRows(key)) {
|
|
112
|
+
for (const el of titleElsOf(item)) writeTitle(el, want);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
104
117
|
if (pendingKeys.has(key)) return;
|
|
105
118
|
pendingKeys.add(key);
|
|
106
119
|
setTimeout(() => {
|
|
@@ -138,7 +151,12 @@ function scan() {
|
|
|
138
151
|
if (!enabled || !pinned || pinned.size === 0) return;
|
|
139
152
|
for (const item of document.querySelectorAll(TASK_SEL)) {
|
|
140
153
|
const key = isMismatch(item);
|
|
141
|
-
if (key)
|
|
154
|
+
if (!key) { seenRows.add(item); continue; }
|
|
155
|
+
// 新上屏的失配行立即钉回(项目展开时旧名闪现 1 秒+,用户不知道点哪个);
|
|
156
|
+
// 已在屏上的失配走原路径(复核后写,避免把刚提交的重命名改回旧名)
|
|
157
|
+
const fresh = !seenRows.has(item);
|
|
158
|
+
seenRows.add(item);
|
|
159
|
+
scheduleEnforce(key, { immediate: fresh });
|
|
142
160
|
}
|
|
143
161
|
}
|
|
144
162
|
|