glad-web 1.0.14 → 1.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commands/web.js +13 -2
- package/lib/session/pty-manager.js +14 -0
- package/lib/session/session-manager.js +10 -0
- package/lib/web/index.html +53 -26
- package/package.json +1 -1
package/lib/commands/web.js
CHANGED
|
@@ -344,6 +344,8 @@ async function webCommand(options) {
|
|
|
344
344
|
|
|
345
345
|
ws.sessionId = sessionId;
|
|
346
346
|
const session = sessionManager.get(sessionId);
|
|
347
|
+
const isReconnect = session.hasConnectedWebClient;
|
|
348
|
+
session.hasConnectedWebClient = true;
|
|
347
349
|
if (!session.resizeOwner) {
|
|
348
350
|
session.resizeOwner = ws;
|
|
349
351
|
}
|
|
@@ -352,7 +354,11 @@ async function webCommand(options) {
|
|
|
352
354
|
// Send catchup output. TUI tools may skip the raw circular buffer, so fall
|
|
353
355
|
// back to the rendered/text history snapshot instead of reconnecting blank.
|
|
354
356
|
const catchup = sessionManager.getCatchupOutput(sessionId);
|
|
355
|
-
|
|
357
|
+
ws.needsTuiRedraw = ['antigravity', 'claude-code', 'codex', 'gemini'].includes(session.tool.key)
|
|
358
|
+
&& (isReconnect || (catchup && catchup.source === 'rendered-history'));
|
|
359
|
+
if (ws.needsTuiRedraw) {
|
|
360
|
+
ws.send(JSON.stringify({ type: 'reset' }));
|
|
361
|
+
} else if (catchup && catchup.data) {
|
|
356
362
|
sessionManager.logWsCatchupOutput(sessionId, catchup);
|
|
357
363
|
ws.send(JSON.stringify({ type: 'output', data: catchup.data }));
|
|
358
364
|
}
|
|
@@ -365,7 +371,12 @@ async function webCommand(options) {
|
|
|
365
371
|
}
|
|
366
372
|
if (payload.type === 'resize' && session.resizeOwner === ws) {
|
|
367
373
|
sessionManager.logWsResize(sessionId, payload.cols, payload.rows);
|
|
368
|
-
|
|
374
|
+
if (ws.needsTuiRedraw) {
|
|
375
|
+
ws.needsTuiRedraw = false;
|
|
376
|
+
sessionManager.redraw(sessionId, payload.cols, payload.rows);
|
|
377
|
+
} else {
|
|
378
|
+
sessionManager.resize(sessionId, payload.cols, payload.rows);
|
|
379
|
+
}
|
|
369
380
|
}
|
|
370
381
|
} catch (e) {
|
|
371
382
|
logger.error('WS Message Error: ' + e.message);
|
|
@@ -197,6 +197,20 @@ class PTYManager {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
redraw(cols, rows) {
|
|
201
|
+
if (!this.ptyProcess) return false;
|
|
202
|
+
|
|
203
|
+
const targetCols = Math.max(2, Number.parseInt(cols, 10) || 80);
|
|
204
|
+
const targetRows = Math.max(1, Number.parseInt(rows, 10) || 24);
|
|
205
|
+
const pulseCols = targetCols > 2 ? targetCols - 1 : targetCols + 1;
|
|
206
|
+
|
|
207
|
+
this.ptyProcess.resize(pulseCols, targetRows);
|
|
208
|
+
setTimeout(() => {
|
|
209
|
+
if (this.ptyProcess) this.ptyProcess.resize(targetCols, targetRows);
|
|
210
|
+
}, 30);
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
200
214
|
setupLocalResizeListener() {
|
|
201
215
|
if (!process.stdout.isTTY) return;
|
|
202
216
|
this.localResizeListener = () => {
|
|
@@ -97,6 +97,7 @@ class SessionManager extends EventEmitter {
|
|
|
97
97
|
inputSeq: 0,
|
|
98
98
|
completionReadInputSeq: 0,
|
|
99
99
|
resizeOwner: null,
|
|
100
|
+
hasConnectedWebClient: false,
|
|
100
101
|
hasUnreadCompletion: false,
|
|
101
102
|
write: data => this.write(id, data),
|
|
102
103
|
isRunning: () => this.has(id) && ptyManager.isRunning(),
|
|
@@ -137,6 +138,15 @@ class SessionManager extends EventEmitter {
|
|
|
137
138
|
return true;
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
redraw(id, cols, rows) {
|
|
142
|
+
const session = this.get(id);
|
|
143
|
+
if (!session) return false;
|
|
144
|
+
if (session.renderedHistory) {
|
|
145
|
+
session.renderedHistory.resize(cols, rows);
|
|
146
|
+
}
|
|
147
|
+
return session.ptyManager.redraw(cols, rows);
|
|
148
|
+
}
|
|
149
|
+
|
|
140
150
|
rename(id, name) {
|
|
141
151
|
const session = this.get(id);
|
|
142
152
|
if (!session || !name) return null;
|
package/lib/web/index.html
CHANGED
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
#shortcut-rail::-webkit-scrollbar { display: none; }
|
|
53
53
|
.shortcut-group { flex: 0 0 calc(100vw - 28px); display: grid; gap: 8px; scroll-snap-align: start; }
|
|
54
54
|
.simple-shortcuts { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
55
|
-
.complex-shortcuts { grid-template-columns: repeat(
|
|
55
|
+
.complex-shortcuts { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
56
56
|
.key-btn, .command-key { flex: 0 0 auto; min-width: 0; height: 40px; border: 1px solid rgba(255,255,255,0.11); border-radius: 12px; background: rgba(255,255,255,0.08); color: #d1d5db; font-size: 12px; font-weight: 700; letter-spacing: 0; cursor: pointer; display: inline-flex; align-items: center; justify-content: center; box-sizing: border-box; white-space: nowrap; }
|
|
57
57
|
.key-btn { font-size: 11px; }
|
|
58
58
|
.shortcut-group .key-btn, .shortcut-group .command-key { width: 100%; }
|
|
@@ -174,9 +174,10 @@
|
|
|
174
174
|
<div class="key-btn" data-key="esc">Esc</div>
|
|
175
175
|
<div class="key-btn" data-key="tab">Tab</div>
|
|
176
176
|
</div>
|
|
177
|
-
<div class="shortcut-group complex-shortcuts">
|
|
177
|
+
<div class="shortcut-group complex-shortcuts" data-role="tool-shortcuts">
|
|
178
|
+
<div class="key-btn special-key" data-key="left">←</div>
|
|
179
|
+
<div class="key-btn special-key" data-key="right">→</div>
|
|
178
180
|
<div class="key-btn" data-key="ctrl-c">Ctrl+C</div>
|
|
179
|
-
<div class="key-btn" data-key="ctrl-y" data-role="tool-action-shortcut" title="Send Ctrl+Y">Ctrl+Y</div>
|
|
180
181
|
<button class="command-key" data-command="/model" title="Send /model">/model</button>
|
|
181
182
|
<button class="command-key" data-command="/resume" title="Send /resume">/resume</button>
|
|
182
183
|
<button class="command-key" data-command="/stat" data-role="usage-shortcut" title="Send /stat">/stat</button>
|
|
@@ -831,28 +832,53 @@
|
|
|
831
832
|
}
|
|
832
833
|
|
|
833
834
|
function updateToolShortcuts(toolKey) {
|
|
834
|
-
const
|
|
835
|
-
const
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
835
|
+
const usesUsageCommand = ['antigravity', 'claude-code'].includes(toolKey);
|
|
836
|
+
const usageCommand = usesUsageCommand ? '/usage' : '/stat';
|
|
837
|
+
const defaultShortcuts = [
|
|
838
|
+
{ key: 'left', label: '←', special: true },
|
|
839
|
+
{ key: 'right', label: '→', special: true },
|
|
840
|
+
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
841
|
+
{ command: '/model' },
|
|
842
|
+
{ command: '/resume' },
|
|
843
|
+
{ command: usageCommand }
|
|
844
|
+
];
|
|
845
|
+
const toolShortcuts = {
|
|
846
|
+
codex: [
|
|
847
|
+
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
848
|
+
{ key: 'codex-perm', label: '/perm', title: 'Send /perm' },
|
|
849
|
+
{ command: '/model' },
|
|
850
|
+
{ command: '/resume' },
|
|
851
|
+
{ command: '/stat' }
|
|
852
|
+
],
|
|
853
|
+
gemini: [
|
|
854
|
+
{ key: 'ctrl-c', label: 'Ctrl+C' },
|
|
855
|
+
{ key: 'ctrl-y', label: 'Ctrl+Y', title: 'Send Ctrl+Y' },
|
|
856
|
+
{ command: '/model' },
|
|
857
|
+
{ command: '/resume' },
|
|
858
|
+
{ command: '/stat' }
|
|
859
|
+
]
|
|
860
|
+
};
|
|
861
|
+
const shortcuts = toolShortcuts[toolKey] || defaultShortcuts;
|
|
862
|
+
const shortcutGroup = document.querySelector('[data-role="tool-shortcuts"]');
|
|
863
|
+
if (!shortcutGroup) return;
|
|
864
|
+
|
|
865
|
+
shortcutGroup.style.gridTemplateColumns = `repeat(${shortcuts.length}, minmax(0, 1fr))`;
|
|
866
|
+
shortcutGroup.replaceChildren(...shortcuts.map(shortcut => {
|
|
867
|
+
const button = document.createElement(shortcut.command ? 'button' : 'div');
|
|
868
|
+
button.className = shortcut.command
|
|
869
|
+
? 'command-key'
|
|
870
|
+
: `key-btn${shortcut.special ? ' special-key' : ''}`;
|
|
871
|
+
if (shortcut.command) {
|
|
872
|
+
button.dataset.command = shortcut.command;
|
|
873
|
+
button.textContent = shortcut.command;
|
|
874
|
+
button.title = `Send ${shortcut.command}`;
|
|
875
|
+
} else {
|
|
876
|
+
button.dataset.key = shortcut.key;
|
|
877
|
+
button.textContent = shortcut.label;
|
|
878
|
+
button.title = shortcut.title || '';
|
|
879
|
+
}
|
|
880
|
+
return button;
|
|
881
|
+
}));
|
|
856
882
|
}
|
|
857
883
|
|
|
858
884
|
function joinSession(id, sessionName, toolKey = null) {
|
|
@@ -901,6 +927,7 @@
|
|
|
901
927
|
currentSocket.onmessage = (e) => {
|
|
902
928
|
const msg = JSON.parse(e.data);
|
|
903
929
|
if (msg.type === 'output') term.write(msg.data);
|
|
930
|
+
if (msg.type === 'reset') term.reset();
|
|
904
931
|
if (msg.type === 'exit') showLobby();
|
|
905
932
|
};
|
|
906
933
|
}
|
|
@@ -1153,7 +1180,7 @@
|
|
|
1153
1180
|
setTimeout(() => { sendWS('\r'); }, 1000);
|
|
1154
1181
|
return;
|
|
1155
1182
|
}
|
|
1156
|
-
const sequences = { up: '\x1b[A', down: '\x1b[B', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
|
|
1183
|
+
const sequences = { up: '\x1b[A', down: '\x1b[B', left: '\x1b[D', right: '\x1b[C', enter: '\r', esc: '\x1b', tab: '\t', 'ctrl-c': '\x03', 'ctrl-y': '\x19' };
|
|
1157
1184
|
if (sequences[key]) sendWS(sequences[key]);
|
|
1158
1185
|
});
|
|
1159
1186
|
|