@co0ontty/wand 1.14.3 → 1.14.6
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/dist/web-ui/content/scripts.js +236 -49
- package/dist/web-ui/content/styles.css +62 -4
- package/package.json +1 -1
|
@@ -109,6 +109,9 @@
|
|
|
109
109
|
loginChecked: false,
|
|
110
110
|
bootstrapping: true,
|
|
111
111
|
sessionsDrawerOpen: false,
|
|
112
|
+
sidebarPinned: (function() {
|
|
113
|
+
try { return localStorage.getItem("wand-sidebar-pinned") === "true"; } catch (e) { return false; }
|
|
114
|
+
})(),
|
|
112
115
|
modalOpen: false,
|
|
113
116
|
presetValue: "",
|
|
114
117
|
cwdValue: "",
|
|
@@ -652,6 +655,7 @@
|
|
|
652
655
|
if (!el) return false;
|
|
653
656
|
switch (kind) {
|
|
654
657
|
case "tool-card":
|
|
658
|
+
case "diff":
|
|
655
659
|
return !el.classList.contains("collapsed");
|
|
656
660
|
case "thinking":
|
|
657
661
|
return el.classList.contains("expanded") && !el.classList.contains("collapsed");
|
|
@@ -672,7 +676,8 @@
|
|
|
672
676
|
function applyExpandedState(el, kind, expanded) {
|
|
673
677
|
if (!el) return;
|
|
674
678
|
switch (kind) {
|
|
675
|
-
case "tool-card":
|
|
679
|
+
case "tool-card":
|
|
680
|
+
case "diff": {
|
|
676
681
|
el.classList.toggle("collapsed", !expanded);
|
|
677
682
|
break;
|
|
678
683
|
}
|
|
@@ -932,6 +937,10 @@
|
|
|
932
937
|
// Suppress CSS transitions during initial DOM build
|
|
933
938
|
document.documentElement.classList.add("no-transition");
|
|
934
939
|
|
|
940
|
+
// Apply persisted pin state before rendering
|
|
941
|
+
if (state.sidebarPinned && !isMobileLayout()) {
|
|
942
|
+
state.sessionsDrawerOpen = true;
|
|
943
|
+
}
|
|
935
944
|
app.innerHTML = isLoggedIn ? renderAppShell() : renderLogin();
|
|
936
945
|
// Reset chat render tracking since DOM was fully replaced
|
|
937
946
|
resetChatRenderCache();
|
|
@@ -1083,8 +1092,8 @@
|
|
|
1083
1092
|
|
|
1084
1093
|
return '<div class="app-container">' +
|
|
1085
1094
|
'<div id="sessions-drawer-backdrop" class="drawer-backdrop' + drawerClass + '"></div>' +
|
|
1086
|
-
'<div class="main-layout' + (state.sessionsDrawerOpen ? ' sidebar-open' : '') + '">' +
|
|
1087
|
-
'<aside id="sessions-drawer" class="sidebar' + drawerClass + '">' +
|
|
1095
|
+
'<div class="main-layout' + (state.sessionsDrawerOpen ? ' sidebar-open' : '') + (state.sidebarPinned && !isMobileLayout() ? ' sidebar-pinned' : '') + '">' +
|
|
1096
|
+
'<aside id="sessions-drawer" class="sidebar' + drawerClass + (state.sidebarPinned && !isMobileLayout() ? ' pinned' : '') + '">' +
|
|
1088
1097
|
'<div class="sidebar-header">' +
|
|
1089
1098
|
'<div class="sidebar-header-main">' +
|
|
1090
1099
|
'<div class="topbar-logo-icon">W</div>' +
|
|
@@ -1098,6 +1107,9 @@
|
|
|
1098
1107
|
'<button id="sidebar-refresh-btn" class="btn btn-ghost btn-sm" type="button" title="刷新页面">' +
|
|
1099
1108
|
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10"/></svg>' +
|
|
1100
1109
|
'</button>' +
|
|
1110
|
+
'<button id="sidebar-pin-btn" class="btn btn-ghost btn-sm sidebar-pin-toggle' + (state.sidebarPinned ? ' pinned' : '') + '" type="button" title="' + (state.sidebarPinned ? '取消固定侧栏' : '固定侧栏') + '">' +
|
|
1111
|
+
'<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="17" x2="12" y2="22"/><path d="M5 17h14v-1.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V6h1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4h1v4.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24z"/></svg>' +
|
|
1112
|
+
'</button>' +
|
|
1101
1113
|
'<button id="close-drawer-button" class="btn btn-ghost btn-sm sidebar-close" type="button" aria-label="关闭菜单">×</button>' +
|
|
1102
1114
|
'</div>' +
|
|
1103
1115
|
'</div>' +
|
|
@@ -1379,6 +1391,17 @@
|
|
|
1379
1391
|
'<label class="field-label" for="cfg-notif-bubble">\u5e94\u7528\u5185\u901a\u77e5\u6c14\u6ce1</label>' +
|
|
1380
1392
|
'</div>' +
|
|
1381
1393
|
'<p class="hint" style="margin-top:0;margin-bottom:10px">\u5728\u9875\u9762\u9876\u90e8\u5f39\u51fa\u6d6e\u52a8\u901a\u77e5\u6c14\u6ce1</p>' +
|
|
1394
|
+
'<div id="native-sound-section" class="settings-notification-section hidden" style="margin-top:6px">' +
|
|
1395
|
+
'<div class="settings-section-title">\u7cfb\u7edf\u901a\u77e5\u94c3\u58f0</div>' +
|
|
1396
|
+
'<div class="settings-about-row">' +
|
|
1397
|
+
'<span class="settings-label">\u94c3\u58f0</span>' +
|
|
1398
|
+
'<div style="display:flex;align-items:center;gap:6px">' +
|
|
1399
|
+
'<select id="native-sound-select" class="field-select" style="min-width:100px"></select>' +
|
|
1400
|
+
'<button id="native-sound-preview" class="btn btn-ghost btn-sm">\u25b6 \u8bd5\u542c</button>' +
|
|
1401
|
+
'</div>' +
|
|
1402
|
+
'</div>' +
|
|
1403
|
+
'<p class="hint" style="margin-top:0">\u9009\u62e9 Android \u7cfb\u7edf\u901a\u77e5\u4f7f\u7528\u7684\u94c3\u58f0</p>' +
|
|
1404
|
+
'</div>' +
|
|
1382
1405
|
'<div class="settings-notification-section" style="margin-top:6px">' +
|
|
1383
1406
|
'<div class="settings-section-title">\u6d4f\u89c8\u5668\u901a\u77e5</div>' +
|
|
1384
1407
|
'<div class="settings-about-row">' +
|
|
@@ -2774,11 +2797,12 @@
|
|
|
2774
2797
|
}
|
|
2775
2798
|
|
|
2776
2799
|
window.__tcToggle = function(e, headerEl) {
|
|
2777
|
-
var card = headerEl.closest(".tool-use-card");
|
|
2800
|
+
var card = headerEl.closest(".tool-use-card") || headerEl.closest(".inline-diff");
|
|
2778
2801
|
if (card) {
|
|
2779
2802
|
var wasCollapsed = card.classList.contains("collapsed");
|
|
2780
2803
|
card.classList.toggle("collapsed");
|
|
2781
|
-
|
|
2804
|
+
var expandKind = card.dataset.expandKind || "tool-card";
|
|
2805
|
+
persistElementExpandState(card, expandKind);
|
|
2782
2806
|
// Lazy-load truncated content on expand
|
|
2783
2807
|
if (wasCollapsed && card.dataset.truncated === "true" && card.dataset.loaded !== "true") {
|
|
2784
2808
|
var toolUseId = card.dataset.toolUseId;
|
|
@@ -3130,6 +3154,8 @@
|
|
|
3130
3154
|
if (drawerBackdrop) drawerBackdrop.addEventListener("click", closeSessionsDrawer);
|
|
3131
3155
|
var closeDrawerBtn = document.getElementById("close-drawer-button");
|
|
3132
3156
|
if (closeDrawerBtn) closeDrawerBtn.addEventListener("click", closeSessionsDrawer);
|
|
3157
|
+
var pinBtn = document.getElementById("sidebar-pin-btn");
|
|
3158
|
+
if (pinBtn) pinBtn.addEventListener("click", toggleSidebarPin);
|
|
3133
3159
|
var homeBtn = document.getElementById("sidebar-home-btn");
|
|
3134
3160
|
if (homeBtn) homeBtn.addEventListener("click", function() {
|
|
3135
3161
|
state.selectedId = null;
|
|
@@ -3237,6 +3263,35 @@
|
|
|
3237
3263
|
var notifTestBtn = document.getElementById("notification-test-btn");
|
|
3238
3264
|
if (notifTestBtn) notifTestBtn.addEventListener("click", testNotification);
|
|
3239
3265
|
updateNotificationStatus();
|
|
3266
|
+
// Native notification sound selector (APK only)
|
|
3267
|
+
if (_hasNativeBridge && typeof WandNative.getAvailableSounds === "function") {
|
|
3268
|
+
var nativeSoundSection = document.getElementById("native-sound-section");
|
|
3269
|
+
var nativeSoundSelect = document.getElementById("native-sound-select");
|
|
3270
|
+
var nativeSoundPreview = document.getElementById("native-sound-preview");
|
|
3271
|
+
if (nativeSoundSection && nativeSoundSelect) {
|
|
3272
|
+
nativeSoundSection.classList.remove("hidden");
|
|
3273
|
+
try {
|
|
3274
|
+
var sounds = JSON.parse(WandNative.getAvailableSounds());
|
|
3275
|
+
var current = WandNative.getNotificationSound();
|
|
3276
|
+
nativeSoundSelect.innerHTML = "";
|
|
3277
|
+
for (var si = 0; si < sounds.length; si++) {
|
|
3278
|
+
var opt = document.createElement("option");
|
|
3279
|
+
opt.value = sounds[si].id;
|
|
3280
|
+
opt.textContent = sounds[si].name;
|
|
3281
|
+
if (sounds[si].id === current) opt.selected = true;
|
|
3282
|
+
nativeSoundSelect.appendChild(opt);
|
|
3283
|
+
}
|
|
3284
|
+
nativeSoundSelect.addEventListener("change", function() {
|
|
3285
|
+
try { WandNative.setNotificationSound(nativeSoundSelect.value); } catch (_e) {}
|
|
3286
|
+
});
|
|
3287
|
+
if (nativeSoundPreview) {
|
|
3288
|
+
nativeSoundPreview.addEventListener("click", function() {
|
|
3289
|
+
try { WandNative.previewSound(nativeSoundSelect.value); } catch (_e) {}
|
|
3290
|
+
});
|
|
3291
|
+
}
|
|
3292
|
+
} catch (_e) {}
|
|
3293
|
+
}
|
|
3294
|
+
}
|
|
3240
3295
|
var newSessBtn = document.getElementById("topbar-new-session-button");
|
|
3241
3296
|
if (newSessBtn) newSessBtn.addEventListener("click", openSessionModal);
|
|
3242
3297
|
var drawerNewSessBtn = document.getElementById("drawer-new-session-button");
|
|
@@ -3803,7 +3858,9 @@
|
|
|
3803
3858
|
} else {
|
|
3804
3859
|
selectSession(sessionId);
|
|
3805
3860
|
}
|
|
3806
|
-
|
|
3861
|
+
if (!state.sidebarPinned || isMobileLayout()) {
|
|
3862
|
+
closeSessionsDrawer();
|
|
3863
|
+
}
|
|
3807
3864
|
}
|
|
3808
3865
|
|
|
3809
3866
|
function handleSessionItemClick(event) {
|
|
@@ -4308,6 +4365,12 @@
|
|
|
4308
4365
|
} else {
|
|
4309
4366
|
updateTerminalJumpToBottomButton();
|
|
4310
4367
|
}
|
|
4368
|
+
// When switching sessions, re-fit the terminal so the PTY receives
|
|
4369
|
+
// the correct dimensions for this client's viewport.
|
|
4370
|
+
if (sessionChanged && state.fitAddon) {
|
|
4371
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
4372
|
+
scheduleTerminalResize(true);
|
|
4373
|
+
}
|
|
4311
4374
|
return wrote || sessionChanged;
|
|
4312
4375
|
}
|
|
4313
4376
|
|
|
@@ -4382,6 +4445,26 @@
|
|
|
4382
4445
|
state.fitAddon = fitAddonConstructor ? new fitAddonConstructor() : null;
|
|
4383
4446
|
if (state.fitAddon) {
|
|
4384
4447
|
state.terminal.loadAddon(state.fitAddon);
|
|
4448
|
+
// Patch: FitAddon subtracts 14px for a scrollbar that CSS hides;
|
|
4449
|
+
// recalculate cols without the scrollbar deduction.
|
|
4450
|
+
var _origPropose = state.fitAddon.proposeDimensions;
|
|
4451
|
+
state.fitAddon.proposeDimensions = function() {
|
|
4452
|
+
var result = _origPropose.call(state.fitAddon);
|
|
4453
|
+
if (result && state.terminal) {
|
|
4454
|
+
try {
|
|
4455
|
+
var core = state.terminal._core;
|
|
4456
|
+
var cellW = core._renderService.dimensions.css.cell.width;
|
|
4457
|
+
var el = state.terminal.element;
|
|
4458
|
+
if (cellW > 0 && el && el.parentElement) {
|
|
4459
|
+
var pw = Math.max(0, parseInt(window.getComputedStyle(el.parentElement).getPropertyValue("width")));
|
|
4460
|
+
var es = window.getComputedStyle(el);
|
|
4461
|
+
var ePad = parseInt(es.getPropertyValue("padding-left")) + parseInt(es.getPropertyValue("padding-right"));
|
|
4462
|
+
result.cols = Math.max(2, Math.floor((pw - ePad) / cellW));
|
|
4463
|
+
}
|
|
4464
|
+
} catch(e) {}
|
|
4465
|
+
}
|
|
4466
|
+
return result;
|
|
4467
|
+
};
|
|
4385
4468
|
} else {
|
|
4386
4469
|
console.error("[wand] xterm fit addon failed to load; continuing without fit support.");
|
|
4387
4470
|
}
|
|
@@ -4400,6 +4483,24 @@
|
|
|
4400
4483
|
// Retry-based fit: wait for browser to complete layout before measuring and fitting
|
|
4401
4484
|
if (state.fitAddon) {
|
|
4402
4485
|
ensureTerminalFit();
|
|
4486
|
+
// Secondary fit after fonts are loaded — FitAddon measures character
|
|
4487
|
+
// dimensions from the rendered font; if a custom web font (e.g. Geist
|
|
4488
|
+
// Mono) hasn't loaded yet the initial fit() uses fallback metrics and
|
|
4489
|
+
// computes too few columns.
|
|
4490
|
+
if (document.fonts && document.fonts.ready) {
|
|
4491
|
+
document.fonts.ready.then(function() {
|
|
4492
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
4493
|
+
ensureTerminalFit();
|
|
4494
|
+
});
|
|
4495
|
+
}
|
|
4496
|
+
// Safety-net fit after layout has fully stabilised (CSS transitions,
|
|
4497
|
+
// deferred reflows, late font loads, etc.)
|
|
4498
|
+
setTimeout(function() {
|
|
4499
|
+
if (state.terminal && state.fitAddon) {
|
|
4500
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
4501
|
+
ensureTerminalFit();
|
|
4502
|
+
}
|
|
4503
|
+
}, 500);
|
|
4403
4504
|
}
|
|
4404
4505
|
|
|
4405
4506
|
var viewport = getTerminalViewport();
|
|
@@ -5170,6 +5271,22 @@
|
|
|
5170
5271
|
subscribeToSession(id);
|
|
5171
5272
|
}
|
|
5172
5273
|
|
|
5274
|
+
function updatePinState() {
|
|
5275
|
+
var drawer = document.getElementById("sessions-drawer");
|
|
5276
|
+
var mainLayout = document.querySelector(".main-layout");
|
|
5277
|
+
var pinBtn = document.getElementById("sidebar-pin-btn");
|
|
5278
|
+
if (drawer) {
|
|
5279
|
+
drawer.classList.toggle("pinned", state.sidebarPinned && !isMobileLayout());
|
|
5280
|
+
}
|
|
5281
|
+
if (mainLayout) {
|
|
5282
|
+
mainLayout.classList.toggle("sidebar-pinned", state.sidebarPinned && !isMobileLayout());
|
|
5283
|
+
}
|
|
5284
|
+
if (pinBtn) {
|
|
5285
|
+
pinBtn.classList.toggle("pinned", state.sidebarPinned);
|
|
5286
|
+
pinBtn.title = state.sidebarPinned ? "取消固定侧栏" : "固定侧栏";
|
|
5287
|
+
}
|
|
5288
|
+
}
|
|
5289
|
+
|
|
5173
5290
|
function updateDrawerState() {
|
|
5174
5291
|
var drawer = document.getElementById("sessions-drawer");
|
|
5175
5292
|
var backdrop = document.getElementById("sessions-drawer-backdrop");
|
|
@@ -5187,9 +5304,11 @@
|
|
|
5187
5304
|
if (toggleBtn) {
|
|
5188
5305
|
toggleBtn.classList.toggle("active", state.sessionsDrawerOpen);
|
|
5189
5306
|
}
|
|
5307
|
+
updatePinState();
|
|
5190
5308
|
}
|
|
5191
5309
|
|
|
5192
5310
|
function toggleSessionsDrawer() {
|
|
5311
|
+
if (state.sidebarPinned && !isMobileLayout()) return;
|
|
5193
5312
|
state.sessionsDrawerOpen = !state.sessionsDrawerOpen;
|
|
5194
5313
|
if (state.sessionsDrawerOpen && isMobileLayout()) {
|
|
5195
5314
|
state.filePanelOpen = false;
|
|
@@ -5201,12 +5320,38 @@
|
|
|
5201
5320
|
}
|
|
5202
5321
|
|
|
5203
5322
|
function closeSessionsDrawer() {
|
|
5323
|
+
if (state.sidebarPinned && !isMobileLayout()) return;
|
|
5204
5324
|
if (!state.sessionsDrawerOpen) return;
|
|
5205
5325
|
closeSwipedItem();
|
|
5206
5326
|
state.sessionsDrawerOpen = false;
|
|
5207
5327
|
updateLayoutState();
|
|
5208
5328
|
}
|
|
5209
5329
|
|
|
5330
|
+
function toggleSidebarPin() {
|
|
5331
|
+
if (isMobileLayout()) return;
|
|
5332
|
+
state.sidebarPinned = !state.sidebarPinned;
|
|
5333
|
+
try {
|
|
5334
|
+
localStorage.setItem("wand-sidebar-pinned", String(state.sidebarPinned));
|
|
5335
|
+
} catch (e) {}
|
|
5336
|
+
if (state.sidebarPinned) {
|
|
5337
|
+
state.sessionsDrawerOpen = true;
|
|
5338
|
+
}
|
|
5339
|
+
updateLayoutState();
|
|
5340
|
+
// Refit terminal after padding-left transition completes
|
|
5341
|
+
var mainLayout = document.querySelector(".main-layout");
|
|
5342
|
+
if (mainLayout) {
|
|
5343
|
+
var onEnd = function(e) {
|
|
5344
|
+
if (e.propertyName === "padding-left") {
|
|
5345
|
+
mainLayout.removeEventListener("transitionend", onEnd);
|
|
5346
|
+
scheduleTerminalResize(true);
|
|
5347
|
+
}
|
|
5348
|
+
};
|
|
5349
|
+
mainLayout.addEventListener("transitionend", onEnd);
|
|
5350
|
+
}
|
|
5351
|
+
// Fallback refit in case transition doesn't fire
|
|
5352
|
+
setTimeout(function() { scheduleTerminalResize(true); }, 350);
|
|
5353
|
+
}
|
|
5354
|
+
|
|
5210
5355
|
// Store last focused element for focus trap
|
|
5211
5356
|
var lastFocusedElement = null;
|
|
5212
5357
|
var focusTrapHandler = null;
|
|
@@ -5472,6 +5617,13 @@
|
|
|
5472
5617
|
if (typeof WandNative !== "undefined" && typeof WandNative.getAppIcon === "function") {
|
|
5473
5618
|
try { _updateAppIconSelection(WandNative.getAppIcon() || "shorthair"); } catch (_e) {}
|
|
5474
5619
|
}
|
|
5620
|
+
// Sync native notification sound selector (APK only)
|
|
5621
|
+
if (_hasNativeBridge && typeof WandNative.getNotificationSound === "function") {
|
|
5622
|
+
try {
|
|
5623
|
+
var nsSel = document.getElementById("native-sound-select");
|
|
5624
|
+
if (nsSel) nsSel.value = WandNative.getNotificationSound();
|
|
5625
|
+
} catch (_e) {}
|
|
5626
|
+
}
|
|
5475
5627
|
}
|
|
5476
5628
|
}
|
|
5477
5629
|
|
|
@@ -9295,7 +9447,22 @@
|
|
|
9295
9447
|
state.resizeObserver = new ResizeObserver(function() { scheduleTerminalResize(true); });
|
|
9296
9448
|
state.resizeObserver.observe(output);
|
|
9297
9449
|
}
|
|
9298
|
-
|
|
9450
|
+
var lastKnownDesktop = !isMobileLayout();
|
|
9451
|
+
state.resizeHandler = function() {
|
|
9452
|
+
scheduleTerminalResize(true);
|
|
9453
|
+
// Handle sidebar pin state across mobile/desktop breakpoint
|
|
9454
|
+
var isDesktop = !isMobileLayout();
|
|
9455
|
+
if (lastKnownDesktop !== isDesktop) {
|
|
9456
|
+
lastKnownDesktop = isDesktop;
|
|
9457
|
+
if (!isDesktop && state.sidebarPinned && state.sessionsDrawerOpen) {
|
|
9458
|
+
state.sessionsDrawerOpen = false;
|
|
9459
|
+
updateDrawerState();
|
|
9460
|
+
} else if (isDesktop && state.sidebarPinned && !state.sessionsDrawerOpen) {
|
|
9461
|
+
state.sessionsDrawerOpen = true;
|
|
9462
|
+
updateDrawerState();
|
|
9463
|
+
}
|
|
9464
|
+
}
|
|
9465
|
+
};
|
|
9299
9466
|
window.addEventListener("resize", state.resizeHandler);
|
|
9300
9467
|
// Also listen to visualViewport resize for pinch-zoom / browser zoom
|
|
9301
9468
|
if (window.visualViewport) {
|
|
@@ -9365,8 +9532,24 @@
|
|
|
9365
9532
|
updateTerminalJumpToBottomButton();
|
|
9366
9533
|
}
|
|
9367
9534
|
|
|
9535
|
+
function sendTerminalResize(cols, rows) {
|
|
9536
|
+
if (!state.selectedId) return;
|
|
9537
|
+
var selectedSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
9538
|
+
if (isStructuredSession(selectedSess)) return;
|
|
9539
|
+
var nextSize = { cols: cols, rows: rows };
|
|
9540
|
+
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
9541
|
+
state.lastResize = nextSize;
|
|
9542
|
+
fetch("/api/sessions/" + state.selectedId + "/resize", {
|
|
9543
|
+
method: "POST",
|
|
9544
|
+
headers: { "Content-Type": "application/json" },
|
|
9545
|
+
credentials: "same-origin",
|
|
9546
|
+
body: JSON.stringify(nextSize)
|
|
9547
|
+
}).catch(function() {});
|
|
9548
|
+
}
|
|
9549
|
+
}
|
|
9550
|
+
|
|
9368
9551
|
function ensureTerminalFit() {
|
|
9369
|
-
var maxAttempts =
|
|
9552
|
+
var maxAttempts = 20;
|
|
9370
9553
|
var attempt = 0;
|
|
9371
9554
|
function tryFit() {
|
|
9372
9555
|
attempt++;
|
|
@@ -9374,19 +9557,19 @@
|
|
|
9374
9557
|
if (shouldResizeTerminalViewport() && state.fitAddon) {
|
|
9375
9558
|
state.fitAddon.fit();
|
|
9376
9559
|
maybeScrollTerminalToBottom("resize");
|
|
9377
|
-
if (state.
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9560
|
+
if (state.terminal) {
|
|
9561
|
+
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
9562
|
+
}
|
|
9563
|
+
// Validate: if the fitted cols look suspiciously small relative to
|
|
9564
|
+
// the container width, schedule another attempt — the font metrics
|
|
9565
|
+
// or layout may not have settled yet.
|
|
9566
|
+
var output = document.getElementById("output");
|
|
9567
|
+
if (output && state.terminal) {
|
|
9568
|
+
var containerW = output.getBoundingClientRect().width;
|
|
9569
|
+
var expectedMinCols = Math.floor(containerW / 20); // very conservative estimate
|
|
9570
|
+
if (state.terminal.cols < expectedMinCols && attempt < maxAttempts) {
|
|
9571
|
+
requestAnimationFrame(tryFit);
|
|
9572
|
+
return;
|
|
9390
9573
|
}
|
|
9391
9574
|
}
|
|
9392
9575
|
} else if (attempt < maxAttempts) {
|
|
@@ -9419,27 +9602,7 @@
|
|
|
9419
9602
|
maybeScrollTerminalToBottom("resize");
|
|
9420
9603
|
}
|
|
9421
9604
|
|
|
9422
|
-
|
|
9423
|
-
cols: state.terminal.cols,
|
|
9424
|
-
rows: state.terminal.rows
|
|
9425
|
-
};
|
|
9426
|
-
|
|
9427
|
-
if (!state.selectedId) return;
|
|
9428
|
-
|
|
9429
|
-
// Skip resize for structured sessions (no PTY)
|
|
9430
|
-
var resizeSess = state.sessions.find(function(s) { return s.id === state.selectedId; });
|
|
9431
|
-
if (isStructuredSession(resizeSess)) return;
|
|
9432
|
-
|
|
9433
|
-
// Only send resize API call if dimensions actually changed
|
|
9434
|
-
if (state.lastResize.cols !== nextSize.cols || state.lastResize.rows !== nextSize.rows) {
|
|
9435
|
-
state.lastResize = nextSize;
|
|
9436
|
-
fetch("/api/sessions/" + state.selectedId + "/resize", {
|
|
9437
|
-
method: "POST",
|
|
9438
|
-
headers: { "Content-Type": "application/json" },
|
|
9439
|
-
credentials: "same-origin",
|
|
9440
|
-
body: JSON.stringify(nextSize)
|
|
9441
|
-
}).catch(function() {});
|
|
9442
|
-
}
|
|
9605
|
+
sendTerminalResize(state.terminal.cols, state.terminal.rows);
|
|
9443
9606
|
}
|
|
9444
9607
|
|
|
9445
9608
|
function startPolling() {
|
|
@@ -9482,6 +9645,12 @@
|
|
|
9482
9645
|
subscribeToSession(state.selectedId);
|
|
9483
9646
|
// Flush pending messages after reconnection
|
|
9484
9647
|
flushPendingMessages();
|
|
9648
|
+
// Re-fit terminal on reconnect — the viewport may have changed
|
|
9649
|
+
// while disconnected, and the PTY needs up-to-date dimensions.
|
|
9650
|
+
if (state.terminal && state.fitAddon) {
|
|
9651
|
+
state.terminalViewportSize = { width: 0, height: 0 };
|
|
9652
|
+
ensureTerminalFit();
|
|
9653
|
+
}
|
|
9485
9654
|
};
|
|
9486
9655
|
|
|
9487
9656
|
ws.onmessage = function(event) {
|
|
@@ -10335,7 +10504,7 @@
|
|
|
10335
10504
|
// Only expand the single newest tool card (first chat-message = newest due to column-reverse)
|
|
10336
10505
|
var firstMsg = chatMessages.querySelector(".chat-message:not(.system-info)");
|
|
10337
10506
|
if (firstMsg) {
|
|
10338
|
-
var cards = firstMsg.querySelectorAll(".tool-use-card");
|
|
10507
|
+
var cards = firstMsg.querySelectorAll(".tool-use-card, .inline-diff[data-expand-key]");
|
|
10339
10508
|
if (cards.length > 0) {
|
|
10340
10509
|
var firstCard = cards[0];
|
|
10341
10510
|
var firstCardKey = getElementExpandKey(firstCard);
|
|
@@ -10345,6 +10514,8 @@
|
|
|
10345
10514
|
for (var ci = 1; ci < cards.length; ci++) {
|
|
10346
10515
|
var cardKey = getElementExpandKey(cards[ci]);
|
|
10347
10516
|
if (getPersistedExpandState(cardKey) === null) {
|
|
10517
|
+
// Never collapse unanswered AskUserQuestion cards
|
|
10518
|
+
if (cards[ci].classList.contains("ask-user") && !cards[ci].classList.contains("ask-user-answered")) continue;
|
|
10348
10519
|
cards[ci].classList.add("collapsed");
|
|
10349
10520
|
}
|
|
10350
10521
|
}
|
|
@@ -10360,10 +10531,13 @@
|
|
|
10360
10531
|
// Collapse all tool-use cards except those in the new message elements (marked with animate-in)
|
|
10361
10532
|
// newEls: NodeList/Array of newly added message elements, or null to keep only the first card expanded
|
|
10362
10533
|
function collapseOldToolCards(container, newEls) {
|
|
10363
|
-
var allCards = container.querySelectorAll(".tool-use-card");
|
|
10534
|
+
var allCards = container.querySelectorAll(".tool-use-card, .inline-diff[data-expand-key]");
|
|
10364
10535
|
allCards.forEach(function(c) {
|
|
10365
10536
|
var cardKey = getElementExpandKey(c);
|
|
10366
10537
|
if (getPersistedExpandState(cardKey) !== null) return;
|
|
10538
|
+
// Never collapse unanswered AskUserQuestion cards — the user
|
|
10539
|
+
// needs to interact with the options.
|
|
10540
|
+
if (c.classList.contains("ask-user") && !c.classList.contains("ask-user-answered")) return;
|
|
10367
10541
|
// Keep expanded if this card is inside a newly added message
|
|
10368
10542
|
if (newEls) {
|
|
10369
10543
|
for (var i = 0; i < newEls.length; i++) {
|
|
@@ -10483,11 +10657,13 @@
|
|
|
10483
10657
|
smartScrollToBottom(chatMessages);
|
|
10484
10658
|
});
|
|
10485
10659
|
var newestMsgEl = chatMessages.querySelector(".chat-message");
|
|
10486
|
-
var allCards = chatMessages.querySelectorAll(".tool-use-card");
|
|
10660
|
+
var allCards = chatMessages.querySelectorAll(".tool-use-card, .inline-diff[data-expand-key]");
|
|
10487
10661
|
var newestCard = null;
|
|
10488
10662
|
allCards.forEach(function(c) {
|
|
10489
10663
|
var cardKey = getElementExpandKey(c);
|
|
10490
10664
|
if (getPersistedExpandState(cardKey) !== null) return;
|
|
10665
|
+
// Never collapse unanswered AskUserQuestion cards
|
|
10666
|
+
if (c.classList.contains("ask-user") && !c.classList.contains("ask-user-answered")) return;
|
|
10491
10667
|
if (newestMsgEl && newestMsgEl.contains(c)) {
|
|
10492
10668
|
if (!newestCard) newestCard = c;
|
|
10493
10669
|
else c.classList.add("collapsed");
|
|
@@ -12047,10 +12223,11 @@
|
|
|
12047
12223
|
return "";
|
|
12048
12224
|
}
|
|
12049
12225
|
|
|
12050
|
-
function renderDiffTool(block, toolResult, toolName) {
|
|
12226
|
+
function renderDiffTool(block, toolResult, toolName, messageKey, index) {
|
|
12051
12227
|
var inputData = block.input || {};
|
|
12052
12228
|
var path = inputData.file_path || inputData.path || "";
|
|
12053
12229
|
var fileName = path.split("/").pop() || path;
|
|
12230
|
+
var toolId = block.id || "tool-" + toolName + "-" + (typeof index === "number" ? index : 0);
|
|
12054
12231
|
|
|
12055
12232
|
var oldStr = inputData.old_string || "";
|
|
12056
12233
|
var newStr = inputData.new_string || inputData.content || "";
|
|
@@ -12095,16 +12272,26 @@
|
|
|
12095
12272
|
statusText = "执行中";
|
|
12096
12273
|
}
|
|
12097
12274
|
|
|
12275
|
+
// Expand state: respect cardDefaults.editCards and persisted state
|
|
12276
|
+
var expandKey = buildExpandKey("diff", [messageKey, toolId || index, index]);
|
|
12277
|
+
var persistedExpanded = getPersistedExpandState(expandKey);
|
|
12278
|
+
var cardDefaultExpand = !!(state.config && state.config.cardDefaults && state.config.cardDefaults.editCards);
|
|
12279
|
+
var shouldExpand = persistedExpanded === null ? (statusClass === "diff-pending" || cardDefaultExpand) : persistedExpanded;
|
|
12280
|
+
var collapsedClass = shouldExpand ? "" : " collapsed";
|
|
12281
|
+
|
|
12098
12282
|
// If only one column has content, show full width
|
|
12099
12283
|
var bothCols = leftCol && rightCol;
|
|
12100
12284
|
var colClass = bothCols ? "diff-col-half" : "diff-col-full";
|
|
12101
12285
|
|
|
12102
|
-
return '<div class="inline-diff" data-tool-name="' + escapeHtml(toolName) + '"
|
|
12103
|
-
'
|
|
12286
|
+
return '<div class="inline-diff' + collapsedClass + '" data-tool-name="' + escapeHtml(toolName) + '"' +
|
|
12287
|
+
' data-expand-kind="diff" data-expand-key="' + escapeHtml(expandKey) + '"' +
|
|
12288
|
+
' data-tool-use-id="' + escapeHtml(toolId) + '">' +
|
|
12289
|
+
'<div class="diff-header" onclick="__tcToggle(event,this)">' +
|
|
12104
12290
|
'<span class="diff-file-icon"></span>' +
|
|
12105
12291
|
'<span class="diff-file-name">' + escapeHtml(fileName) + '</span>' +
|
|
12106
12292
|
'<span class="diff-path">' + escapeHtml(path) + '</span>' +
|
|
12107
12293
|
'<span class="diff-status ' + statusClass + '">' + statusText + '</span>' +
|
|
12294
|
+
'<span class="diff-toggle">▼</span>' +
|
|
12108
12295
|
'</div>' +
|
|
12109
12296
|
'<div class="diff-body">' +
|
|
12110
12297
|
'<div class="diff-columns">' +
|
|
@@ -12138,7 +12325,7 @@
|
|
|
12138
12325
|
|
|
12139
12326
|
// ── Diff-style: Edit, Write, MultiEdit
|
|
12140
12327
|
if (toolName === "Edit" || toolName === "Write" || toolName === "MultiEdit") {
|
|
12141
|
-
return renderDiffTool(block, toolResult, toolName);
|
|
12328
|
+
return renderDiffTool(block, toolResult, toolName, messageKey, index);
|
|
12142
12329
|
}
|
|
12143
12330
|
|
|
12144
12331
|
// ── AskUserQuestion tool — special card with batch submit
|
|
@@ -370,12 +370,20 @@
|
|
|
370
370
|
transition: padding-left var(--transition-normal);
|
|
371
371
|
}
|
|
372
372
|
/* .sidebar-open class toggled for semantic purposes only; sidebar overlays without resizing main layout */
|
|
373
|
-
.sidebar-open .input-panel {
|
|
373
|
+
.sidebar-open:not(.sidebar-pinned) .input-panel {
|
|
374
374
|
opacity: 0;
|
|
375
375
|
pointer-events: none;
|
|
376
376
|
transition: opacity 0.2s ease;
|
|
377
377
|
}
|
|
378
378
|
|
|
379
|
+
/* ===== 侧边栏常驻 ===== */
|
|
380
|
+
.main-layout.sidebar-pinned {
|
|
381
|
+
padding-left: var(--sidebar-width);
|
|
382
|
+
}
|
|
383
|
+
.main-layout.sidebar-pinned .floating-sidebar-toggle {
|
|
384
|
+
display: none;
|
|
385
|
+
}
|
|
386
|
+
|
|
379
387
|
/* ===== 抽屉背景遮罩 ===== */
|
|
380
388
|
.drawer-backdrop {
|
|
381
389
|
position: fixed;
|
|
@@ -426,6 +434,27 @@
|
|
|
426
434
|
opacity: 1;
|
|
427
435
|
}
|
|
428
436
|
|
|
437
|
+
.sidebar.pinned {
|
|
438
|
+
transform: translateX(0);
|
|
439
|
+
pointer-events: auto;
|
|
440
|
+
opacity: 1;
|
|
441
|
+
box-shadow: none;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
.sidebar.pinned .sidebar-close {
|
|
445
|
+
display: none;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/* ===== 图钉按钮 ===== */
|
|
449
|
+
.sidebar-pin-toggle {
|
|
450
|
+
flex-shrink: 0;
|
|
451
|
+
transition: transform var(--transition-fast), color var(--transition-fast);
|
|
452
|
+
}
|
|
453
|
+
.sidebar-pin-toggle.pinned {
|
|
454
|
+
color: var(--primary);
|
|
455
|
+
transform: rotate(45deg);
|
|
456
|
+
}
|
|
457
|
+
|
|
429
458
|
/* ===== 侧边栏头部 ===== */
|
|
430
459
|
.sidebar-header {
|
|
431
460
|
display: flex;
|
|
@@ -2139,7 +2168,6 @@
|
|
|
2139
2168
|
radial-gradient(circle at top right, rgba(91, 58, 34, 0.2), transparent 35%),
|
|
2140
2169
|
radial-gradient(circle at bottom left, rgba(197, 101, 61, 0.08), transparent 40%),
|
|
2141
2170
|
linear-gradient(180deg, #2a221c 0%, #1f1b17 50%, #1a1613 100%);
|
|
2142
|
-
padding: 10px;
|
|
2143
2171
|
overflow: hidden;
|
|
2144
2172
|
min-height: 0;
|
|
2145
2173
|
min-width: 0;
|
|
@@ -2165,8 +2193,6 @@
|
|
|
2165
2193
|
left: 0;
|
|
2166
2194
|
right: 0;
|
|
2167
2195
|
bottom: 0;
|
|
2168
|
-
width: 100%;
|
|
2169
|
-
height: 100%;
|
|
2170
2196
|
padding: 0;
|
|
2171
2197
|
overflow: hidden;
|
|
2172
2198
|
}
|
|
@@ -4084,6 +4110,26 @@
|
|
|
4084
4110
|
padding: 6px 10px;
|
|
4085
4111
|
background: rgba(0, 0, 0, 0.03);
|
|
4086
4112
|
font-size: 0.75rem;
|
|
4113
|
+
cursor: pointer;
|
|
4114
|
+
}
|
|
4115
|
+
.diff-header:hover {
|
|
4116
|
+
background: rgba(0, 0, 0, 0.05);
|
|
4117
|
+
}
|
|
4118
|
+
.diff-toggle {
|
|
4119
|
+
font-size: 0.625rem;
|
|
4120
|
+
color: var(--text-muted);
|
|
4121
|
+
transition: transform 0.3s var(--ease-spring);
|
|
4122
|
+
flex-shrink: 0;
|
|
4123
|
+
margin-left: auto;
|
|
4124
|
+
}
|
|
4125
|
+
.inline-diff.collapsed .diff-toggle {
|
|
4126
|
+
transform: rotate(-90deg);
|
|
4127
|
+
}
|
|
4128
|
+
.inline-diff.collapsed .diff-body {
|
|
4129
|
+
max-height: 0;
|
|
4130
|
+
overflow: hidden;
|
|
4131
|
+
opacity: 0;
|
|
4132
|
+
transition: max-height 0.35s var(--ease-out-expo), opacity 0.25s ease;
|
|
4087
4133
|
}
|
|
4088
4134
|
.diff-file-icon {
|
|
4089
4135
|
display: none;
|
|
@@ -6068,6 +6114,18 @@
|
|
|
6068
6114
|
|
|
6069
6115
|
/* 平板适配 */
|
|
6070
6116
|
@media (max-width: 768px) {
|
|
6117
|
+
.sidebar-pin-toggle { display: none; }
|
|
6118
|
+
.sidebar.pinned:not(.open) {
|
|
6119
|
+
transform: translateX(-100%);
|
|
6120
|
+
pointer-events: none;
|
|
6121
|
+
opacity: 0;
|
|
6122
|
+
}
|
|
6123
|
+
.main-layout.sidebar-pinned {
|
|
6124
|
+
padding-left: 0;
|
|
6125
|
+
}
|
|
6126
|
+
.main-layout.sidebar-pinned .floating-sidebar-toggle {
|
|
6127
|
+
display: inline-flex;
|
|
6128
|
+
}
|
|
6071
6129
|
.app-container {
|
|
6072
6130
|
--layout-main-file-panel-width: 0px;
|
|
6073
6131
|
}
|