@co0ontty/wand 1.17.5 → 1.18.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.
|
@@ -44,6 +44,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
44
44
|
worktreeEnabled?: boolean;
|
|
45
45
|
provider?: SessionProvider;
|
|
46
46
|
model?: string;
|
|
47
|
+
reuseId?: string;
|
|
47
48
|
}): SessionSnapshot;
|
|
48
49
|
list(): SessionSnapshot[];
|
|
49
50
|
/** Return lightweight snapshots for the session list (no output/messages). */
|
|
@@ -69,6 +70,7 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
69
70
|
private emitTask;
|
|
70
71
|
resize(id: string, cols: number, rows: number): SessionSnapshot;
|
|
71
72
|
stop(id: string): SessionSnapshot;
|
|
73
|
+
private cleanupRecord;
|
|
72
74
|
delete(id: string): void;
|
|
73
75
|
private deleteClaudeCache;
|
|
74
76
|
runStartupCommands(): SessionSnapshot[];
|
|
@@ -104,8 +106,8 @@ export declare class ProcessManager extends EventEmitter {
|
|
|
104
106
|
/**
|
|
105
107
|
* Auto-recover the most recent exited session that has a Claude session ID.
|
|
106
108
|
* Only resumes one session per server start, using the most recent eligible
|
|
107
|
-
* session.
|
|
108
|
-
* `autoRecovered: true
|
|
109
|
+
* session. Reuses the original session ID (in-place resume) and sets
|
|
110
|
+
* `autoRecovered: true`.
|
|
109
111
|
*/
|
|
110
112
|
private autoRecoverExitedSessions;
|
|
111
113
|
private archiveExpiredSessions;
|
package/dist/process-manager.js
CHANGED
|
@@ -608,13 +608,23 @@ export class ProcessManager extends EventEmitter {
|
|
|
608
608
|
this.lastPersistedMessageState.delete(id);
|
|
609
609
|
this.storage.deleteSession(id);
|
|
610
610
|
}
|
|
611
|
+
if (toRemove.length > 0) {
|
|
612
|
+
this.claudeHistoryCache = null;
|
|
613
|
+
}
|
|
611
614
|
}
|
|
612
615
|
start(command, cwd, mode, initialInput, opts) {
|
|
613
616
|
this.assertCommandAllowed(command);
|
|
614
617
|
const baseCwd = cwd
|
|
615
618
|
? path.resolve(process.cwd(), cwd)
|
|
616
619
|
: path.resolve(process.cwd(), this.config.defaultCwd);
|
|
617
|
-
const id = randomUUID();
|
|
620
|
+
const id = opts?.reuseId || randomUUID();
|
|
621
|
+
if (opts?.reuseId) {
|
|
622
|
+
const oldRecord = this.sessions.get(id);
|
|
623
|
+
if (oldRecord) {
|
|
624
|
+
this.cleanupRecord(oldRecord);
|
|
625
|
+
this.sessions.delete(id);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
618
628
|
const worktreeSetup = opts?.worktreeEnabled
|
|
619
629
|
? prepareSessionWorktree({ cwd: baseCwd, sessionId: id })
|
|
620
630
|
: null;
|
|
@@ -690,6 +700,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
690
700
|
}
|
|
691
701
|
this.sessions.set(id, record);
|
|
692
702
|
this.persist(record);
|
|
703
|
+
if (initialClaudeSessionId) {
|
|
704
|
+
this.claudeHistoryCache = null;
|
|
705
|
+
}
|
|
693
706
|
this.cleanupOldSessions();
|
|
694
707
|
this.lifecycleManager.register(id, "initializing");
|
|
695
708
|
const shellArgs = this.buildShellArgs(processedCommand);
|
|
@@ -800,6 +813,7 @@ export class ProcessManager extends EventEmitter {
|
|
|
800
813
|
const bridgeSessionId = rec.ptyBridge?.getClaudeSessionId();
|
|
801
814
|
if (bridgeSessionId && bridgeSessionId !== rec.claudeSessionId) {
|
|
802
815
|
rec.claudeSessionId = bridgeSessionId;
|
|
816
|
+
this.claudeHistoryCache = null;
|
|
803
817
|
process.stderr.write(`[wand] Captured Claude session ID: ${bridgeSessionId}\n`);
|
|
804
818
|
}
|
|
805
819
|
if (!rec.claudeSessionId && rec.knownClaudeTaskIds) {
|
|
@@ -1083,6 +1097,41 @@ export class ProcessManager extends EventEmitter {
|
|
|
1083
1097
|
this.persist(record);
|
|
1084
1098
|
return this.snapshot(record);
|
|
1085
1099
|
}
|
|
1100
|
+
cleanupRecord(record) {
|
|
1101
|
+
if (record.taskDebounceTimer) {
|
|
1102
|
+
clearTimeout(record.taskDebounceTimer);
|
|
1103
|
+
record.taskDebounceTimer = null;
|
|
1104
|
+
}
|
|
1105
|
+
if (record.claudeTaskDiscoveryTimer) {
|
|
1106
|
+
clearTimeout(record.claudeTaskDiscoveryTimer);
|
|
1107
|
+
record.claudeTaskDiscoveryTimer = null;
|
|
1108
|
+
}
|
|
1109
|
+
if (record.initialInputTimer) {
|
|
1110
|
+
clearTimeout(record.initialInputTimer);
|
|
1111
|
+
record.initialInputTimer = null;
|
|
1112
|
+
}
|
|
1113
|
+
const pendingPersist = this.persistDebounceTimers.get(record.id);
|
|
1114
|
+
if (pendingPersist) {
|
|
1115
|
+
clearTimeout(pendingPersist);
|
|
1116
|
+
this.persistDebounceTimers.delete(record.id);
|
|
1117
|
+
}
|
|
1118
|
+
if (record.status === "running") {
|
|
1119
|
+
record.stopRequested = true;
|
|
1120
|
+
if (record.childProcess) {
|
|
1121
|
+
record.childProcess.kill();
|
|
1122
|
+
record.childProcess = null;
|
|
1123
|
+
}
|
|
1124
|
+
if (record.ptyProcess) {
|
|
1125
|
+
record.ptyProcess.kill();
|
|
1126
|
+
record.ptyProcess = null;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
if (record.ptyBridge) {
|
|
1130
|
+
record.ptyBridge.removeAllListeners();
|
|
1131
|
+
record.ptyBridge = null;
|
|
1132
|
+
}
|
|
1133
|
+
this.lifecycleManager.unregister(record.id);
|
|
1134
|
+
}
|
|
1086
1135
|
delete(id) {
|
|
1087
1136
|
const record = this.mustGet(id);
|
|
1088
1137
|
// Always clear pending timers
|
|
@@ -1135,6 +1184,9 @@ export class ProcessManager extends EventEmitter {
|
|
|
1135
1184
|
this.sessions.delete(id);
|
|
1136
1185
|
this.lastPersistedMessageState.delete(id);
|
|
1137
1186
|
this.lifecycleManager.unregister(id);
|
|
1187
|
+
if (record.claudeSessionId) {
|
|
1188
|
+
this.claudeHistoryCache = null;
|
|
1189
|
+
}
|
|
1138
1190
|
}
|
|
1139
1191
|
deleteClaudeCache(record) {
|
|
1140
1192
|
if (!record.claudeSessionId)
|
|
@@ -1357,8 +1409,8 @@ export class ProcessManager extends EventEmitter {
|
|
|
1357
1409
|
/**
|
|
1358
1410
|
* Auto-recover the most recent exited session that has a Claude session ID.
|
|
1359
1411
|
* Only resumes one session per server start, using the most recent eligible
|
|
1360
|
-
* session.
|
|
1361
|
-
* `autoRecovered: true
|
|
1412
|
+
* session. Reuses the original session ID (in-place resume) and sets
|
|
1413
|
+
* `autoRecovered: true`.
|
|
1362
1414
|
*/
|
|
1363
1415
|
autoRecoverExitedSessions() {
|
|
1364
1416
|
// Find eligible exited sessions
|
|
@@ -1393,19 +1445,12 @@ export class ProcessManager extends EventEmitter {
|
|
|
1393
1445
|
}
|
|
1394
1446
|
console.error(`[ProcessManager] Auto-recovering session ${original.id} with Claude session ID ${original.claudeSessionId}`);
|
|
1395
1447
|
const resumeCommand = `${original.command.trim()} --resume ${original.claudeSessionId}`;
|
|
1396
|
-
let newRecord = null;
|
|
1397
1448
|
try {
|
|
1398
1449
|
const snapshot = this.start(resumeCommand, original.cwd, original.mode, undefined, {
|
|
1399
|
-
|
|
1450
|
+
reuseId: original.id,
|
|
1400
1451
|
autoRecovered: true
|
|
1401
1452
|
});
|
|
1402
|
-
|
|
1403
|
-
if (!newRecord)
|
|
1404
|
-
return;
|
|
1405
|
-
// Set resumedToSessionId on the original session
|
|
1406
|
-
original.resumedToSessionId = snapshot.id;
|
|
1407
|
-
this.storage.saveSession(this.snapshot(original));
|
|
1408
|
-
console.error(`[ProcessManager] Auto-recovered session ${snapshot.id} from ${original.id}`);
|
|
1453
|
+
console.error(`[ProcessManager] Auto-recovered session ${snapshot.id} (in-place)`);
|
|
1409
1454
|
}
|
|
1410
1455
|
catch (err) {
|
|
1411
1456
|
console.error(`[ProcessManager] Auto-recovery failed: ${String(err)}`);
|
|
@@ -406,8 +406,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
406
406
|
}
|
|
407
407
|
const newMode = body.mode ? normalizeMode(body.mode, defaultMode) : normalizeMode(existingSession.mode, defaultMode);
|
|
408
408
|
const resumeCommand = `${command} --resume ${claudeSessionId}`;
|
|
409
|
-
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, {
|
|
410
|
-
storage.saveSession({ ...existingSession, resumedToSessionId: newSnapshot.id, archived: true });
|
|
409
|
+
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, { reuseId: sessionId });
|
|
411
410
|
res.status(201).json({ resumedFromSessionId: sessionId, ...newSnapshot });
|
|
412
411
|
}
|
|
413
412
|
catch (error) {
|
|
@@ -444,8 +443,7 @@ export function registerSessionRoutes(app, processes, structured, storage, defau
|
|
|
444
443
|
}
|
|
445
444
|
const newMode = body.mode ? normalizeMode(body.mode, defaultMode) : normalizeMode(existingSession.mode, defaultMode);
|
|
446
445
|
const resumeCommand = `${command} --resume ${claudeSessionId}`;
|
|
447
|
-
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, {
|
|
448
|
-
storage.saveSession({ ...existingSession, resumedToSessionId: newSnapshot.id, archived: true });
|
|
446
|
+
const newSnapshot = processes.start(resumeCommand, existingSession.cwd, newMode, undefined, { reuseId: existingSession.id });
|
|
449
447
|
res.status(201).json({ resumedFromSessionId: existingSession.id, resumedClaudeSessionId: claudeSessionId, ...newSnapshot });
|
|
450
448
|
}
|
|
451
449
|
else {
|
|
@@ -842,6 +842,9 @@
|
|
|
842
842
|
if (!state.ws || (state.ws.readyState !== WebSocket.OPEN && state.ws.readyState !== WebSocket.CONNECTING)) {
|
|
843
843
|
initWebSocket();
|
|
844
844
|
}
|
|
845
|
+
if (state.claudeHistoryLoaded) {
|
|
846
|
+
loadClaudeHistory();
|
|
847
|
+
}
|
|
845
848
|
return loadSessions({ skipSelectedOutputReload: true }).then(function() {
|
|
846
849
|
if (state.selectedId) {
|
|
847
850
|
return loadOutput(state.selectedId);
|
|
@@ -1201,13 +1204,13 @@
|
|
|
1201
1204
|
'<span class="terminal-scale-overlay-divider"></span>' +
|
|
1202
1205
|
'<button id="page-refresh-btn" class="terminal-scale-overlay-btn" type="button" title="刷新页面">↻</button>' +
|
|
1203
1206
|
'</div>' +
|
|
1204
|
-
'<button id="terminal-jump-bottom" class="terminal-jump-bottom' + (state.showTerminalJumpToBottom ? ' visible' : '') + '" type="button" title="回到底部"
|
|
1207
|
+
'<button id="terminal-jump-bottom" class="terminal-jump-bottom' + (state.showTerminalJumpToBottom ? ' visible' : '') + '" type="button" title="回到底部" aria-label="回到底部"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3.5v9M3.5 8l4.5 4.5L12.5 8"/></svg></button>' +
|
|
1205
1208
|
'</div>' +
|
|
1206
1209
|
'<div id="chat-output" class="chat-container hidden">' +
|
|
1207
1210
|
'<div class="chat-overlay-controls">' +
|
|
1208
1211
|
'<button id="chat-follow-toggle" class="chat-follow-toggle topbar-btn' + (state.chatAutoFollow ? ' active' : '') + '" type="button" aria-pressed="' + (state.chatAutoFollow ? 'true' : 'false') + '" title="' + (state.chatAutoFollow ? '追踪底部:开启' : '追踪底部:已暂停') + '">' + (state.chatAutoFollow ? '追底' : '暂停') + '</button>' +
|
|
1209
1212
|
'</div>' +
|
|
1210
|
-
'<button id="chat-jump-bottom" class="chat-jump-bottom' + (state.showChatJumpToBottom ? ' visible' : '') + '" type="button" title="回到底部并继续追底"
|
|
1213
|
+
'<button id="chat-jump-bottom" class="chat-jump-bottom' + (state.showChatJumpToBottom ? ' visible' : '') + '" type="button" title="回到底部并继续追底" aria-label="回到底部"><svg viewBox="0 0 16 16" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8 3.5v9M3.5 8l4.5 4.5L12.5 8"/></svg></button>' +
|
|
1211
1214
|
'</div>' +
|
|
1212
1215
|
'<div id="blank-chat" class="blank-chat' + (state.selectedId ? " hidden" : "") + '">' +
|
|
1213
1216
|
'<div class="blank-chat-inner">' +
|
|
@@ -1499,6 +1502,14 @@
|
|
|
1499
1502
|
'</div>' +
|
|
1500
1503
|
'<p class="hint" style="margin-top:0">\u9009\u62e9 Android \u7cfb\u7edf\u901a\u77e5\u4f7f\u7528\u7684\u94c3\u58f0</p>' +
|
|
1501
1504
|
'</div>' +
|
|
1505
|
+
'<div id="native-haptic-section" class="settings-notification-section hidden" style="margin-top:6px">' +
|
|
1506
|
+
'<div class="settings-section-title">\u89e6\u611f\u53cd\u9988</div>' +
|
|
1507
|
+
'<div class="field field-inline" style="margin:4px 0">' +
|
|
1508
|
+
'<input id="cfg-haptic-enabled" type="checkbox" class="field-checkbox" />' +
|
|
1509
|
+
'<label class="field-label" for="cfg-haptic-enabled">\u542f\u7528\u89e6\u611f\u53cd\u9988</label>' +
|
|
1510
|
+
'</div>' +
|
|
1511
|
+
'<p class="hint" style="margin-top:0">\u6309\u94ae\u64cd\u4f5c\u548c\u4efb\u52a1\u5b8c\u6210\u65f6\u63d0\u4f9b\u632f\u52a8\u53cd\u9988</p>' +
|
|
1512
|
+
'</div>' +
|
|
1502
1513
|
'<div class="settings-notification-section" style="margin-top:6px">' +
|
|
1503
1514
|
'<div class="settings-section-title">\u6d4f\u89c8\u5668\u901a\u77e5</div>' +
|
|
1504
1515
|
'<div class="settings-about-row">' +
|
|
@@ -1857,8 +1868,12 @@
|
|
|
1857
1868
|
}
|
|
1858
1869
|
|
|
1859
1870
|
function getVisibleClaudeHistorySessions() {
|
|
1871
|
+
var managedIds = new Set();
|
|
1872
|
+
state.sessions.forEach(function(s) {
|
|
1873
|
+
if (s.claudeSessionId) managedIds.add(s.claudeSessionId);
|
|
1874
|
+
});
|
|
1860
1875
|
return state.claudeHistory.filter(function(s) {
|
|
1861
|
-
return s.hasConversation && !s.managedByWand;
|
|
1876
|
+
return s.hasConversation && !s.managedByWand && !managedIds.has(s.claudeSessionId);
|
|
1862
1877
|
});
|
|
1863
1878
|
}
|
|
1864
1879
|
|
|
@@ -3483,6 +3498,19 @@
|
|
|
3483
3498
|
} catch (_e) {}
|
|
3484
3499
|
}
|
|
3485
3500
|
}
|
|
3501
|
+
// Native haptic toggle (APK only)
|
|
3502
|
+
if (_hasNativeBridge && typeof WandNative.isHapticEnabled === "function") {
|
|
3503
|
+
var hapticSection = document.getElementById("native-haptic-section");
|
|
3504
|
+
var hapticToggle = document.getElementById("cfg-haptic-enabled");
|
|
3505
|
+
if (hapticSection && hapticToggle) {
|
|
3506
|
+
hapticSection.classList.remove("hidden");
|
|
3507
|
+
try { hapticToggle.checked = WandNative.isHapticEnabled(); } catch (_e) {}
|
|
3508
|
+
hapticToggle.addEventListener("change", function() {
|
|
3509
|
+
try { WandNative.setHapticEnabled(hapticToggle.checked); } catch (_e) {}
|
|
3510
|
+
if (hapticToggle.checked) _vibrate("medium");
|
|
3511
|
+
});
|
|
3512
|
+
}
|
|
3513
|
+
}
|
|
3486
3514
|
var newSessBtn = document.getElementById("topbar-new-session-button");
|
|
3487
3515
|
if (newSessBtn) newSessBtn.addEventListener("click", openSessionModal);
|
|
3488
3516
|
var drawerNewSessBtn = document.getElementById("drawer-new-session-button");
|
|
@@ -4245,16 +4273,14 @@
|
|
|
4245
4273
|
if (!badge) return;
|
|
4246
4274
|
var fullId = badge.dataset.claudeId;
|
|
4247
4275
|
if (!fullId) return;
|
|
4248
|
-
|
|
4249
|
-
|
|
4276
|
+
var original = badge.textContent;
|
|
4277
|
+
copyToClipboard(fullId, null, function() {
|
|
4250
4278
|
badge.textContent = "\u2713 已复制";
|
|
4251
4279
|
badge.classList.add("copied");
|
|
4252
4280
|
setTimeout(function() {
|
|
4253
4281
|
badge.textContent = original;
|
|
4254
4282
|
badge.classList.remove("copied");
|
|
4255
4283
|
}, 1200);
|
|
4256
|
-
}).catch(function() {
|
|
4257
|
-
showToast("复制失败", "error");
|
|
4258
4284
|
});
|
|
4259
4285
|
}
|
|
4260
4286
|
|
|
@@ -4838,6 +4864,9 @@
|
|
|
4838
4864
|
: "向 Codex 发送输入;chat 为解析后的阅读视图";
|
|
4839
4865
|
}
|
|
4840
4866
|
if (session && !isStructuredSession(session) && session.status !== "running") {
|
|
4867
|
+
if (canAutoResumeSession(session)) {
|
|
4868
|
+
return "输入消息...";
|
|
4869
|
+
}
|
|
4841
4870
|
return "会话已结束,无法继续发送";
|
|
4842
4871
|
}
|
|
4843
4872
|
return session && isStructuredSession(session) && session.structuredState && session.structuredState.inFlight
|
|
@@ -5389,6 +5418,7 @@
|
|
|
5389
5418
|
flushCrossSessionQueue();
|
|
5390
5419
|
}
|
|
5391
5420
|
renderCrossSessionQueue();
|
|
5421
|
+
_syncWakeLock();
|
|
5392
5422
|
});
|
|
5393
5423
|
})
|
|
5394
5424
|
.catch(function(e) {
|
|
@@ -5948,6 +5978,12 @@
|
|
|
5948
5978
|
try { localStorage.setItem("wand-notif-volume", String(nativeVol)); } catch (_e) {}
|
|
5949
5979
|
} catch (_e) {}
|
|
5950
5980
|
}
|
|
5981
|
+
if (_hasNativeBridge && typeof WandNative.isHapticEnabled === "function") {
|
|
5982
|
+
try {
|
|
5983
|
+
var hapticEl = document.getElementById("cfg-haptic-enabled");
|
|
5984
|
+
if (hapticEl) hapticEl.checked = WandNative.isHapticEnabled();
|
|
5985
|
+
} catch (_e) {}
|
|
5986
|
+
}
|
|
5951
5987
|
}
|
|
5952
5988
|
}
|
|
5953
5989
|
|
|
@@ -6094,16 +6130,23 @@
|
|
|
6094
6130
|
}
|
|
6095
6131
|
|
|
6096
6132
|
|
|
6097
|
-
function copyToClipboard(text, triggerBtn) {
|
|
6133
|
+
function copyToClipboard(text, triggerBtn, successCallback) {
|
|
6098
6134
|
if (!text) return;
|
|
6099
|
-
|
|
6135
|
+
function onSuccess() {
|
|
6136
|
+
_vibrate("light");
|
|
6137
|
+
if (successCallback) { successCallback(); return; }
|
|
6100
6138
|
if (triggerBtn) {
|
|
6101
6139
|
var orig = triggerBtn.textContent;
|
|
6102
6140
|
triggerBtn.textContent = "已复制";
|
|
6103
6141
|
setTimeout(function() { triggerBtn.textContent = orig; }, 1500);
|
|
6104
6142
|
}
|
|
6105
|
-
}
|
|
6106
|
-
|
|
6143
|
+
}
|
|
6144
|
+
if (_hasNativeBridge && typeof WandNative.copyToClipboard === "function") {
|
|
6145
|
+
try {
|
|
6146
|
+
if (WandNative.copyToClipboard(text) === "ok") { onSuccess(); return; }
|
|
6147
|
+
} catch (_e) {}
|
|
6148
|
+
}
|
|
6149
|
+
navigator.clipboard.writeText(text).then(onSuccess).catch(function() {
|
|
6107
6150
|
var ta = document.createElement("textarea");
|
|
6108
6151
|
ta.value = text;
|
|
6109
6152
|
ta.style.position = "fixed";
|
|
@@ -6112,11 +6155,7 @@
|
|
|
6112
6155
|
ta.select();
|
|
6113
6156
|
document.execCommand("copy");
|
|
6114
6157
|
document.body.removeChild(ta);
|
|
6115
|
-
|
|
6116
|
-
var orig = triggerBtn.textContent;
|
|
6117
|
-
triggerBtn.textContent = "已复制";
|
|
6118
|
-
setTimeout(function() { triggerBtn.textContent = orig; }, 1500);
|
|
6119
|
-
}
|
|
6158
|
+
onSuccess();
|
|
6120
6159
|
});
|
|
6121
6160
|
}
|
|
6122
6161
|
|
|
@@ -8214,7 +8253,6 @@
|
|
|
8214
8253
|
if (!data) return null;
|
|
8215
8254
|
updateSessionSnapshot(data);
|
|
8216
8255
|
updateSessionsList();
|
|
8217
|
-
switchToSessionView(data.id);
|
|
8218
8256
|
subscribeToSession(data.id);
|
|
8219
8257
|
return loadOutput(data.id).then(function() {
|
|
8220
8258
|
focusInputBox(true);
|
|
@@ -8985,6 +9023,9 @@
|
|
|
8985
9023
|
else showToast(data.error, "error");
|
|
8986
9024
|
return null;
|
|
8987
9025
|
}
|
|
9026
|
+
state.claudeHistory = state.claudeHistory.filter(function(s) {
|
|
9027
|
+
return s.claudeSessionId !== claudeSessionId;
|
|
9028
|
+
});
|
|
8988
9029
|
state.selectedId = data.id;
|
|
8989
9030
|
persistSelectedId();
|
|
8990
9031
|
state.drafts[data.id] = "";
|
|
@@ -9014,6 +9055,11 @@
|
|
|
9014
9055
|
console.log("[WAND] resumeSessionFromList sessionId:", sessionId);
|
|
9015
9056
|
return resumeSession(sessionId).then(function(data) {
|
|
9016
9057
|
if (!data) return null;
|
|
9058
|
+
if (data.claudeSessionId) {
|
|
9059
|
+
state.claudeHistory = state.claudeHistory.filter(function(s) {
|
|
9060
|
+
return s.claudeSessionId !== data.claudeSessionId;
|
|
9061
|
+
});
|
|
9062
|
+
}
|
|
9017
9063
|
return activateSession(data).then(function() {
|
|
9018
9064
|
return data;
|
|
9019
9065
|
});
|
|
@@ -9121,11 +9167,13 @@
|
|
|
9121
9167
|
resumeClaudeHistorySession(claudeSessionId, cwd)
|
|
9122
9168
|
.then(function(data) {
|
|
9123
9169
|
if (data && data.id) {
|
|
9170
|
+
state.claudeHistory = state.claudeHistory.filter(function(s) {
|
|
9171
|
+
return s.claudeSessionId !== claudeSessionId;
|
|
9172
|
+
});
|
|
9124
9173
|
state.selectedId = data.id;
|
|
9125
9174
|
persistSelectedId();
|
|
9126
9175
|
state.drafts[data.id] = "";
|
|
9127
|
-
|
|
9128
|
-
selectSession(data.id);
|
|
9176
|
+
activateSession(data).then(function() {
|
|
9129
9177
|
closeSessionsDrawer();
|
|
9130
9178
|
});
|
|
9131
9179
|
}
|
|
@@ -10489,8 +10537,10 @@
|
|
|
10489
10537
|
} else {
|
|
10490
10538
|
endedNotifBody = endedSession ? (endedSession.command || msg.sessionId) : msg.sessionId;
|
|
10491
10539
|
}
|
|
10540
|
+
_vibrate(endedIsError ? "error" : "success");
|
|
10492
10541
|
notifyTaskEnded(msg.sessionId, endedNotifTitle, endedNotifBody);
|
|
10493
10542
|
clearSessionProgressNative(msg.sessionId);
|
|
10543
|
+
_syncWakeLock();
|
|
10494
10544
|
if (msg.sessionId !== state.selectedId || document.hidden) {
|
|
10495
10545
|
showNotificationBubble({
|
|
10496
10546
|
title: endedNotifTitle,
|
|
@@ -10629,6 +10679,7 @@
|
|
|
10629
10679
|
} else {
|
|
10630
10680
|
permBody += "\n" + permDetail;
|
|
10631
10681
|
}
|
|
10682
|
+
_vibrate("medium");
|
|
10632
10683
|
notifyPermissionRequest(msg.sessionId, permBody);
|
|
10633
10684
|
// In-app bubble if not currently viewing this session
|
|
10634
10685
|
if (msg.sessionId !== state.selectedId) {
|
|
@@ -10653,6 +10704,7 @@
|
|
|
10653
10704
|
}
|
|
10654
10705
|
updateSessionSnapshot(statusUpdate);
|
|
10655
10706
|
syncSessionProgressToNative(msg.sessionId);
|
|
10707
|
+
_syncWakeLock();
|
|
10656
10708
|
if (msg.sessionId === state.selectedId) {
|
|
10657
10709
|
updateTaskDisplay();
|
|
10658
10710
|
if (msg.data.approvalStats) {
|
|
@@ -10777,6 +10829,7 @@
|
|
|
10777
10829
|
}
|
|
10778
10830
|
|
|
10779
10831
|
function approvePermission() {
|
|
10832
|
+
_vibrate("light");
|
|
10780
10833
|
if (!state.selectedId) return;
|
|
10781
10834
|
var approveBtn = document.getElementById("approve-permission-btn");
|
|
10782
10835
|
var denyBtn = document.getElementById("deny-permission-btn");
|
|
@@ -10805,6 +10858,7 @@
|
|
|
10805
10858
|
}
|
|
10806
10859
|
|
|
10807
10860
|
function denyPermission() {
|
|
10861
|
+
_vibrate("light");
|
|
10808
10862
|
if (!state.selectedId) return;
|
|
10809
10863
|
var approveBtn = document.getElementById("approve-permission-btn");
|
|
10810
10864
|
var denyBtn = document.getElementById("deny-permission-btn");
|
|
@@ -11507,13 +11561,10 @@
|
|
|
11507
11561
|
var codeBlock = btn.closest(".code-block");
|
|
11508
11562
|
var code = codeBlock ? codeBlock.querySelector("code") : null;
|
|
11509
11563
|
if (code) {
|
|
11510
|
-
|
|
11564
|
+
copyToClipboard(code.textContent || "", null, function() {
|
|
11511
11565
|
btn.textContent = "Copied!";
|
|
11512
11566
|
btn.classList.add("copied");
|
|
11513
|
-
setTimeout(function() {
|
|
11514
|
-
btn.textContent = "Copy";
|
|
11515
|
-
btn.classList.remove("copied");
|
|
11516
|
-
}, 2000);
|
|
11567
|
+
setTimeout(function() { btn.textContent = "Copy"; btn.classList.remove("copied"); }, 2000);
|
|
11517
11568
|
});
|
|
11518
11569
|
}
|
|
11519
11570
|
});
|
|
@@ -11522,25 +11573,20 @@
|
|
|
11522
11573
|
|
|
11523
11574
|
function attachAllCopyHandlers(container) {
|
|
11524
11575
|
container.querySelectorAll(".code-copy").forEach(function(btn) {
|
|
11525
|
-
// Remove existing listeners by cloning
|
|
11526
11576
|
var clone = btn.cloneNode(true);
|
|
11527
11577
|
btn.parentNode.replaceChild(clone, btn);
|
|
11528
11578
|
clone.addEventListener("click", function() {
|
|
11529
11579
|
var codeBlock = clone.closest(".code-block");
|
|
11530
11580
|
var code = codeBlock ? codeBlock.querySelector("code") : null;
|
|
11531
11581
|
if (code) {
|
|
11532
|
-
|
|
11582
|
+
copyToClipboard(code.textContent || "", null, function() {
|
|
11533
11583
|
clone.textContent = "Copied!";
|
|
11534
11584
|
clone.classList.add("copied");
|
|
11535
|
-
setTimeout(function() {
|
|
11536
|
-
clone.textContent = "Copy";
|
|
11537
|
-
clone.classList.remove("copied");
|
|
11538
|
-
}, 2000);
|
|
11585
|
+
setTimeout(function() { clone.textContent = "Copy"; clone.classList.remove("copied"); }, 2000);
|
|
11539
11586
|
});
|
|
11540
11587
|
}
|
|
11541
11588
|
});
|
|
11542
11589
|
});
|
|
11543
|
-
// Attach message-level copy buttons for touch devices
|
|
11544
11590
|
attachMessageCopyButtons(container);
|
|
11545
11591
|
}
|
|
11546
11592
|
|
|
@@ -11560,7 +11606,7 @@
|
|
|
11560
11606
|
btn.addEventListener("click", function(e) {
|
|
11561
11607
|
e.stopPropagation();
|
|
11562
11608
|
var text = bubble.innerText || bubble.textContent || "";
|
|
11563
|
-
|
|
11609
|
+
copyToClipboard(text.trim(), null, function() {
|
|
11564
11610
|
btn.textContent = "已复制";
|
|
11565
11611
|
btn.classList.add("copied");
|
|
11566
11612
|
setTimeout(function() {
|
|
@@ -13629,6 +13675,30 @@
|
|
|
13629
13675
|
var _apkVersionMatch = navigator.userAgent.match(/WandApp\/([^\s]+)/);
|
|
13630
13676
|
var _apkVersion = _apkVersionMatch ? _apkVersionMatch[1] : null;
|
|
13631
13677
|
|
|
13678
|
+
function _vibrate(pattern) {
|
|
13679
|
+
if (!_hasNativeBridge || typeof WandNative.vibrate !== "function") return;
|
|
13680
|
+
try { WandNative.vibrate(pattern || "light"); } catch (_e) {}
|
|
13681
|
+
}
|
|
13682
|
+
|
|
13683
|
+
function _syncWakeLock() {
|
|
13684
|
+
if (!_hasNativeBridge) return;
|
|
13685
|
+
var anyActive = state.sessions.some(function(s) {
|
|
13686
|
+
return !s.archived && (s.status === "running" || s.status === "thinking" || s.status === "initializing");
|
|
13687
|
+
});
|
|
13688
|
+
if (typeof WandNative.setKeepScreenOn === "function") {
|
|
13689
|
+
try { WandNative.setKeepScreenOn(anyActive); } catch (_e) {}
|
|
13690
|
+
}
|
|
13691
|
+
if (anyActive) {
|
|
13692
|
+
if (typeof WandNative.startKeepAlive === "function") {
|
|
13693
|
+
try { WandNative.startKeepAlive(); } catch (_e) {}
|
|
13694
|
+
}
|
|
13695
|
+
} else {
|
|
13696
|
+
if (typeof WandNative.stopKeepAlive === "function") {
|
|
13697
|
+
try { WandNative.stopKeepAlive(); } catch (_e) {}
|
|
13698
|
+
}
|
|
13699
|
+
}
|
|
13700
|
+
}
|
|
13701
|
+
|
|
13632
13702
|
function _getNativePermission() {
|
|
13633
13703
|
if (_hasNativeBridge && typeof WandNative.getPermission === "function") {
|
|
13634
13704
|
try { return WandNative.getPermission(); } catch (_e) {}
|
|
@@ -13848,6 +13918,41 @@
|
|
|
13848
13918
|
try { WandNative.clearSessionProgress(sessionId); } catch (_e) {}
|
|
13849
13919
|
}
|
|
13850
13920
|
|
|
13921
|
+
// ── Android back button handler ──
|
|
13922
|
+
window.handleNativeBack = function() {
|
|
13923
|
+
var settingsModal = document.getElementById("settings-modal");
|
|
13924
|
+
if (settingsModal && !settingsModal.classList.contains("hidden")) {
|
|
13925
|
+
closeSettingsModal();
|
|
13926
|
+
return true;
|
|
13927
|
+
}
|
|
13928
|
+
var sessionModal = document.getElementById("session-modal");
|
|
13929
|
+
if (sessionModal && !sessionModal.classList.contains("hidden")) {
|
|
13930
|
+
closeSessionModal();
|
|
13931
|
+
return true;
|
|
13932
|
+
}
|
|
13933
|
+
var worktreeModal = document.getElementById("worktree-merge-modal");
|
|
13934
|
+
if (worktreeModal && !worktreeModal.classList.contains("hidden")) {
|
|
13935
|
+
closeWorktreeMergeModal();
|
|
13936
|
+
return true;
|
|
13937
|
+
}
|
|
13938
|
+
if (state.filePanelOpen && isMobileLayout()) {
|
|
13939
|
+
setFilePanelOpen(false);
|
|
13940
|
+
return true;
|
|
13941
|
+
}
|
|
13942
|
+
if (state.sessionsDrawerOpen && isMobileLayout()) {
|
|
13943
|
+
closeSessionsDrawer();
|
|
13944
|
+
return true;
|
|
13945
|
+
}
|
|
13946
|
+
if (isMobileLayout() && state.selectedId) {
|
|
13947
|
+
state.selectedId = null;
|
|
13948
|
+
persistSelectedId();
|
|
13949
|
+
state.sessionsDrawerOpen = true;
|
|
13950
|
+
render();
|
|
13951
|
+
return true;
|
|
13952
|
+
}
|
|
13953
|
+
return false;
|
|
13954
|
+
};
|
|
13955
|
+
|
|
13851
13956
|
/**
|
|
13852
13957
|
* Play a soft, rounded notification chime using Web Audio API.
|
|
13853
13958
|
* Two ascending sine tones with smooth gain envelope — gentle on the ears.
|
|
@@ -122,16 +122,19 @@
|
|
|
122
122
|
}
|
|
123
123
|
|
|
124
124
|
/* ===== PWA 独立窗口模式 ===== */
|
|
125
|
+
/* 顶部安全区:用 max() 保底,避免 iPad 横屏/Stage Manager 等 inset 为 0 的场景下贴顶,
|
|
126
|
+
同时为 iPadOS Stage Manager 浮动控件预留视觉缓冲 */
|
|
125
127
|
@media (display-mode: standalone) {
|
|
126
128
|
:root {
|
|
127
129
|
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
130
|
+
--pwa-safe-top: max(env(safe-area-inset-top, 0px), 14px);
|
|
128
131
|
}
|
|
129
132
|
.app-container {
|
|
130
|
-
--pwa-top-inset:
|
|
131
|
-
padding-top:
|
|
133
|
+
--pwa-top-inset: var(--pwa-safe-top);
|
|
134
|
+
padding-top: var(--pwa-safe-top);
|
|
132
135
|
}
|
|
133
136
|
.sidebar-header {
|
|
134
|
-
padding-top: calc(14px +
|
|
137
|
+
padding-top: calc(14px + var(--pwa-safe-top));
|
|
135
138
|
}
|
|
136
139
|
.main-content {
|
|
137
140
|
padding-top: 0;
|
|
@@ -141,13 +144,14 @@
|
|
|
141
144
|
/* iOS Safari PWA fallback (navigator.standalone adds .is-pwa via JS) */
|
|
142
145
|
.is-pwa {
|
|
143
146
|
--safe-bottom: env(safe-area-inset-bottom, 0px);
|
|
147
|
+
--pwa-safe-top: max(env(safe-area-inset-top, 0px), 14px);
|
|
144
148
|
}
|
|
145
149
|
.is-pwa .app-container {
|
|
146
|
-
--pwa-top-inset:
|
|
147
|
-
padding-top:
|
|
150
|
+
--pwa-top-inset: var(--pwa-safe-top);
|
|
151
|
+
padding-top: var(--pwa-safe-top);
|
|
148
152
|
}
|
|
149
153
|
.is-pwa .sidebar-header {
|
|
150
|
-
padding-top: calc(14px +
|
|
154
|
+
padding-top: calc(14px + var(--pwa-safe-top));
|
|
151
155
|
}
|
|
152
156
|
|
|
153
157
|
/* ===== PWA 窗口控件覆盖模式 (Window Controls Overlay) ===== */
|
|
@@ -2220,12 +2224,22 @@
|
|
|
2220
2224
|
display: flex;
|
|
2221
2225
|
align-items: center;
|
|
2222
2226
|
justify-content: flex-start;
|
|
2223
|
-
padding:
|
|
2227
|
+
padding: 6px 10px;
|
|
2224
2228
|
flex-shrink: 0;
|
|
2225
|
-
min-height:
|
|
2229
|
+
min-height: 44px;
|
|
2226
2230
|
gap: 8px;
|
|
2227
2231
|
}
|
|
2228
2232
|
|
|
2233
|
+
/* PWA 模式额外留出顶部缓冲,避免与 iPadOS Stage Manager 等系统浮动控件视觉粘连 */
|
|
2234
|
+
@media (display-mode: standalone) {
|
|
2235
|
+
.main-header-row {
|
|
2236
|
+
padding-top: 10px;
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
.is-pwa .main-header-row {
|
|
2240
|
+
padding-top: 10px;
|
|
2241
|
+
}
|
|
2242
|
+
|
|
2229
2243
|
/* Current task indicator */
|
|
2230
2244
|
.current-task {
|
|
2231
2245
|
display: flex;
|
|
@@ -2603,38 +2617,66 @@
|
|
|
2603
2617
|
.terminal-jump-bottom {
|
|
2604
2618
|
position: absolute;
|
|
2605
2619
|
right: 14px;
|
|
2606
|
-
bottom:
|
|
2620
|
+
bottom: 16px;
|
|
2607
2621
|
display: inline-flex;
|
|
2608
2622
|
align-items: center;
|
|
2609
2623
|
justify-content: center;
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
border:
|
|
2615
|
-
|
|
2616
|
-
background: rgba(24, 20, 17, 0.72);
|
|
2624
|
+
width: 34px;
|
|
2625
|
+
height: 34px;
|
|
2626
|
+
padding: 0;
|
|
2627
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
2628
|
+
border-radius: 50%;
|
|
2629
|
+
background: rgba(28, 22, 18, 0.78);
|
|
2617
2630
|
color: rgba(255, 247, 239, 0.92);
|
|
2618
|
-
box-shadow:
|
|
2619
|
-
|
|
2631
|
+
box-shadow:
|
|
2632
|
+
0 1px 2px rgba(0, 0, 0, 0.25),
|
|
2633
|
+
0 8px 22px rgba(0, 0, 0, 0.32),
|
|
2634
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.06);
|
|
2635
|
+
backdrop-filter: blur(12px);
|
|
2636
|
+
-webkit-backdrop-filter: blur(12px);
|
|
2620
2637
|
cursor: pointer;
|
|
2621
2638
|
z-index: 13;
|
|
2622
2639
|
opacity: 0;
|
|
2623
|
-
transform: translateY(
|
|
2640
|
+
transform: translateY(10px) scale(0.86);
|
|
2624
2641
|
pointer-events: none;
|
|
2625
|
-
transition:
|
|
2642
|
+
transition:
|
|
2643
|
+
opacity 0.26s var(--ease-out-expo),
|
|
2644
|
+
transform 0.32s var(--ease-spring),
|
|
2645
|
+
background 0.18s ease,
|
|
2646
|
+
border-color 0.18s ease,
|
|
2647
|
+
box-shadow 0.2s ease;
|
|
2626
2648
|
}
|
|
2627
2649
|
.terminal-jump-bottom.visible {
|
|
2628
2650
|
opacity: 1;
|
|
2629
2651
|
transform: translateY(0) scale(1);
|
|
2630
2652
|
pointer-events: auto;
|
|
2631
2653
|
}
|
|
2654
|
+
.terminal-jump-bottom svg {
|
|
2655
|
+
transition: transform 0.2s var(--ease-out-expo);
|
|
2656
|
+
}
|
|
2632
2657
|
.terminal-jump-bottom:hover {
|
|
2633
|
-
background: rgba(
|
|
2634
|
-
border-color: rgba(
|
|
2658
|
+
background: rgba(214, 123, 82, 0.92);
|
|
2659
|
+
border-color: rgba(255, 255, 255, 0.18);
|
|
2660
|
+
color: #fff;
|
|
2661
|
+
box-shadow:
|
|
2662
|
+
0 1px 2px rgba(0, 0, 0, 0.28),
|
|
2663
|
+
0 10px 26px rgba(184, 92, 55, 0.36),
|
|
2664
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.18);
|
|
2665
|
+
}
|
|
2666
|
+
.terminal-jump-bottom:hover svg {
|
|
2667
|
+
transform: translateY(1.5px);
|
|
2635
2668
|
}
|
|
2636
2669
|
.terminal-jump-bottom:active {
|
|
2637
|
-
transform: translateY(
|
|
2670
|
+
transform: translateY(0) scale(0.92);
|
|
2671
|
+
transition-duration: 0.08s;
|
|
2672
|
+
}
|
|
2673
|
+
.terminal-jump-bottom:focus-visible {
|
|
2674
|
+
outline: none;
|
|
2675
|
+
box-shadow:
|
|
2676
|
+
0 1px 2px rgba(0, 0, 0, 0.28),
|
|
2677
|
+
0 10px 26px rgba(184, 92, 55, 0.36),
|
|
2678
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.18),
|
|
2679
|
+
0 0 0 3px rgba(214, 123, 82, 0.35);
|
|
2638
2680
|
}
|
|
2639
2681
|
|
|
2640
2682
|
/* Terminal interactive mode indicator */
|
|
@@ -2969,26 +3011,32 @@
|
|
|
2969
3011
|
.chat-jump-bottom {
|
|
2970
3012
|
position: absolute;
|
|
2971
3013
|
right: 14px;
|
|
2972
|
-
bottom:
|
|
3014
|
+
bottom: 16px;
|
|
2973
3015
|
display: inline-flex;
|
|
2974
3016
|
align-items: center;
|
|
2975
3017
|
justify-content: center;
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
border:
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
3018
|
+
width: 34px;
|
|
3019
|
+
height: 34px;
|
|
3020
|
+
padding: 0;
|
|
3021
|
+
border: 1px solid rgba(255, 255, 255, 0.65);
|
|
3022
|
+
border-radius: 50%;
|
|
3023
|
+
background: var(--accent);
|
|
3024
|
+
color: #fff;
|
|
3025
|
+
box-shadow:
|
|
3026
|
+
0 1px 2px rgba(89, 58, 32, 0.18),
|
|
3027
|
+
0 8px 22px rgba(184, 92, 55, 0.28),
|
|
3028
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.25);
|
|
2986
3029
|
cursor: pointer;
|
|
2987
3030
|
z-index: 13;
|
|
2988
3031
|
opacity: 0;
|
|
2989
|
-
transform: translateY(
|
|
3032
|
+
transform: translateY(10px) scale(0.86);
|
|
2990
3033
|
pointer-events: none;
|
|
2991
|
-
transition:
|
|
3034
|
+
transition:
|
|
3035
|
+
opacity 0.26s var(--ease-out-expo),
|
|
3036
|
+
transform 0.32s var(--ease-spring),
|
|
3037
|
+
background 0.18s ease,
|
|
3038
|
+
border-color 0.18s ease,
|
|
3039
|
+
box-shadow 0.2s ease;
|
|
2992
3040
|
}
|
|
2993
3041
|
|
|
2994
3042
|
.chat-jump-bottom.visible {
|
|
@@ -2997,13 +3045,36 @@
|
|
|
2997
3045
|
pointer-events: auto;
|
|
2998
3046
|
}
|
|
2999
3047
|
|
|
3048
|
+
.chat-jump-bottom svg {
|
|
3049
|
+
transition: transform 0.2s var(--ease-out-expo);
|
|
3050
|
+
}
|
|
3051
|
+
|
|
3000
3052
|
.chat-jump-bottom:hover {
|
|
3001
|
-
background:
|
|
3002
|
-
border-color: rgba(
|
|
3053
|
+
background: var(--accent-hover);
|
|
3054
|
+
border-color: rgba(255, 255, 255, 0.85);
|
|
3055
|
+
box-shadow:
|
|
3056
|
+
0 2px 4px rgba(89, 58, 32, 0.20),
|
|
3057
|
+
0 12px 28px rgba(184, 92, 55, 0.38),
|
|
3058
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32);
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
.chat-jump-bottom:hover svg {
|
|
3062
|
+
transform: translateY(1.5px);
|
|
3003
3063
|
}
|
|
3004
3064
|
|
|
3005
3065
|
.chat-jump-bottom:active {
|
|
3006
|
-
transform: translateY(
|
|
3066
|
+
transform: translateY(0) scale(0.92);
|
|
3067
|
+
background: var(--accent-active);
|
|
3068
|
+
transition-duration: 0.08s;
|
|
3069
|
+
}
|
|
3070
|
+
|
|
3071
|
+
.chat-jump-bottom:focus-visible {
|
|
3072
|
+
outline: none;
|
|
3073
|
+
box-shadow:
|
|
3074
|
+
0 2px 4px rgba(89, 58, 32, 0.20),
|
|
3075
|
+
0 12px 28px rgba(184, 92, 55, 0.38),
|
|
3076
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.32),
|
|
3077
|
+
0 0 0 3px rgba(197, 101, 61, 0.32);
|
|
3007
3078
|
}
|
|
3008
3079
|
|
|
3009
3080
|
.chat-container.active { display: flex; }
|
|
@@ -6720,12 +6791,11 @@
|
|
|
6720
6791
|
/* 回到底部按钮 - 紧凑 */
|
|
6721
6792
|
.terminal-jump-bottom,
|
|
6722
6793
|
.chat-jump-bottom {
|
|
6723
|
-
|
|
6724
|
-
|
|
6725
|
-
padding: 0
|
|
6726
|
-
font-size: 0.6875rem;
|
|
6794
|
+
width: 32px;
|
|
6795
|
+
height: 32px;
|
|
6796
|
+
padding: 0;
|
|
6727
6797
|
right: 10px;
|
|
6728
|
-
bottom:
|
|
6798
|
+
bottom: 12px;
|
|
6729
6799
|
}
|
|
6730
6800
|
|
|
6731
6801
|
/* 小键盘 FAB */
|