Add viewport-based PTY resize from dashboard
This commit is contained in:
parent
19917ef614
commit
5948dcaed1
2 changed files with 118 additions and 0 deletions
|
|
@ -1134,6 +1134,14 @@
|
||||||
// Reset rendered length when collapsing or expanding (element gets recreated)
|
// Reset rendered length when collapsing or expanding (element gets recreated)
|
||||||
session.outputRenderedLength = 0;
|
session.outputRenderedLength = 0;
|
||||||
renderSessions();
|
renderSessions();
|
||||||
|
|
||||||
|
// If expanding, resize the PTY to fit the viewport
|
||||||
|
if (session.expanded) {
|
||||||
|
// Wait for DOM to update before measuring
|
||||||
|
setTimeout(() => {
|
||||||
|
handleSessionResize(Number(sessionId));
|
||||||
|
}, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1978,6 +1986,74 @@
|
||||||
// Auto-scroll control
|
// Auto-scroll control
|
||||||
const scrollListeners = new Map();
|
const scrollListeners = new Map();
|
||||||
|
|
||||||
|
// Viewport-based PTY resize
|
||||||
|
let resizeDebounceTimer = null;
|
||||||
|
|
||||||
|
function measureTerminalCols() {
|
||||||
|
// Create a hidden span with monospace font to measure character width
|
||||||
|
const $measure = document.createElement('span');
|
||||||
|
$measure.style.fontFamily = '"SF Mono", Monaco, "Cascadia Code", "Courier New", monospace';
|
||||||
|
$measure.style.fontSize = `${state.settings.fontSize}px`;
|
||||||
|
$measure.style.lineHeight = '1.5';
|
||||||
|
$measure.style.visibility = 'hidden';
|
||||||
|
$measure.style.position = 'absolute';
|
||||||
|
$measure.textContent = 'X'.repeat(100); // Measure 100 chars
|
||||||
|
document.body.appendChild($measure);
|
||||||
|
|
||||||
|
const charWidth = $measure.offsetWidth / 100;
|
||||||
|
document.body.removeChild($measure);
|
||||||
|
|
||||||
|
// Get terminal container width (accounting for padding)
|
||||||
|
const $container = document.querySelector('.session-output');
|
||||||
|
if (!$container) {
|
||||||
|
return 80; // Default fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
const containerWidth = $container.clientWidth - 32; // 16px padding on each side
|
||||||
|
const cols = Math.floor(containerWidth / charWidth);
|
||||||
|
|
||||||
|
// Clamp to reasonable bounds
|
||||||
|
return Math.max(40, Math.min(cols, 300));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function resizeSessionPTY(sessionId, cols, rows) {
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/sessions/${sessionId}/resize`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ cols, rows }),
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error('Failed to resize session PTY:', await res.text());
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error resizing session PTY:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleSessionResize(sessionId) {
|
||||||
|
const cols = measureTerminalCols();
|
||||||
|
const rows = 24; // Reasonable default row count
|
||||||
|
resizeSessionPTY(sessionId, cols, rows);
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleWindowResize() {
|
||||||
|
if (resizeDebounceTimer) {
|
||||||
|
clearTimeout(resizeDebounceTimer);
|
||||||
|
}
|
||||||
|
|
||||||
|
resizeDebounceTimer = setTimeout(() => {
|
||||||
|
// Resize all expanded sessions
|
||||||
|
state.sessions.forEach((session) => {
|
||||||
|
if (session.expanded) {
|
||||||
|
handleSessionResize(session.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, 300); // Debounce for 300ms
|
||||||
|
}
|
||||||
|
|
||||||
function attachScrollListener(sessionId, $outputContainer) {
|
function attachScrollListener(sessionId, $outputContainer) {
|
||||||
// Remove existing listener if present
|
// Remove existing listener if present
|
||||||
const existing = scrollListeners.get(sessionId);
|
const existing = scrollListeners.get(sessionId);
|
||||||
|
|
@ -2041,6 +2117,9 @@
|
||||||
loadSettings();
|
loadSettings();
|
||||||
applySettings();
|
applySettings();
|
||||||
connectSSE();
|
connectSSE();
|
||||||
|
|
||||||
|
// Set up window resize listener for PTY viewport resize
|
||||||
|
window.addEventListener('resize', handleWindowResize);
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -141,6 +141,45 @@ const server = Bun.serve<SessionData>({
|
||||||
return Response.json(sessions);
|
return Response.json(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Resize endpoint for dashboard to resize CLI PTY
|
||||||
|
if (
|
||||||
|
url.pathname.match(/^\/api\/sessions\/\d+\/resize$/) &&
|
||||||
|
req.method === "POST"
|
||||||
|
) {
|
||||||
|
const parts = url.pathname.split("/");
|
||||||
|
const sessionId = Number.parseInt(parts[3] || "", 10);
|
||||||
|
|
||||||
|
if (Number.isNaN(sessionId)) {
|
||||||
|
return new Response("Invalid session ID", { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const body = (await req.json()) as { cols?: unknown; rows?: unknown };
|
||||||
|
if (
|
||||||
|
typeof body.cols !== "number" ||
|
||||||
|
typeof body.rows !== "number" ||
|
||||||
|
body.cols <= 0 ||
|
||||||
|
body.rows <= 0
|
||||||
|
) {
|
||||||
|
return new Response("Missing or invalid cols/rows", { status: 400 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get WebSocket connection for this session
|
||||||
|
const ws = sessionWebSockets.get(sessionId);
|
||||||
|
if (!ws) {
|
||||||
|
return new Response("Session WebSocket not found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send resize command to CLI
|
||||||
|
const message: ServerMessage = {
|
||||||
|
type: "resize",
|
||||||
|
cols: body.cols,
|
||||||
|
rows: body.rows,
|
||||||
|
};
|
||||||
|
ws.send(JSON.stringify(message));
|
||||||
|
|
||||||
|
return Response.json({ success: true });
|
||||||
|
}
|
||||||
|
|
||||||
// Create prompt for a session
|
// Create prompt for a session
|
||||||
if (
|
if (
|
||||||
url.pathname.match(/^\/api\/sessions\/\d+\/prompts$/) &&
|
url.pathname.match(/^\/api\/sessions\/\d+\/prompts$/) &&
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue