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)
|
||||
session.outputRenderedLength = 0;
|
||||
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
|
||||
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) {
|
||||
// Remove existing listener if present
|
||||
const existing = scrollListeners.get(sessionId);
|
||||
|
|
@ -2041,6 +2117,9 @@
|
|||
loadSettings();
|
||||
applySettings();
|
||||
connectSSE();
|
||||
|
||||
// Set up window resize listener for PTY viewport resize
|
||||
window.addEventListener('resize', handleWindowResize);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -141,6 +141,45 @@ const server = Bun.serve<SessionData>({
|
|||
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
|
||||
if (
|
||||
url.pathname.match(/^\/api\/sessions\/\d+\/prompts$/) &&
|
||||
|
|
|
|||
Loading…
Reference in a new issue