Spaces:
Configuration error
Configuration error
Aman Nindra
Enhance backend terminal session management and frontend command comparison features. Added new API endpoints for terminal session creation, input handling, resizing, and stopping. Updated frontend to support real-time command broadcasting and display runtime comparisons between jobs. Improved styling and layout for terminal panes and comparison statistics.
4dcc016 | import { useCallback, useEffect, useRef, useState } from 'react' | |
| import { | |
| createOrAttachSession, | |
| openTerminalSocket, | |
| resizeTerminalSession, | |
| sendTerminalInput, | |
| stopTerminalSession, | |
| } from '../api/terminal' | |
| const BUFFER_LIMIT = 160000 | |
| function trimBuffer(text) { | |
| return text.length > BUFFER_LIMIT ? text.slice(-BUFFER_LIMIT) : text | |
| } | |
| export function useTerminalSession(jobId) { | |
| const [session, setSession] = useState(null) | |
| const [buffer, setBuffer] = useState('') | |
| const [connectionState, setConnectionState] = useState('connecting') | |
| const [error, setError] = useState('') | |
| const [lastOutputAt, setLastOutputAt] = useState(null) | |
| const socketRef = useRef(null) | |
| const resizeRef = useRef({ cols: null, rows: null }) | |
| const attachSocket = useCallback((sessionId) => { | |
| if (socketRef.current) { | |
| socketRef.current.close() | |
| } | |
| const socket = openTerminalSocket(sessionId) | |
| socketRef.current = socket | |
| setConnectionState('connecting') | |
| socket.addEventListener('open', () => { | |
| setConnectionState('connected') | |
| }) | |
| socket.addEventListener('message', (event) => { | |
| const payload = JSON.parse(event.data) | |
| if (payload.type === 'snapshot') { | |
| setSession(payload.session) | |
| setBuffer(payload.buffer || '') | |
| return | |
| } | |
| if (payload.type === 'output') { | |
| setLastOutputAt(Date.now()) | |
| setBuffer((previous) => trimBuffer(previous + payload.data)) | |
| return | |
| } | |
| if (payload.type === 'exit') { | |
| setSession((previous) => | |
| previous | |
| ? { | |
| ...previous, | |
| status: payload.status, | |
| exit_code: payload.exit_code, | |
| finished_at: payload.finished_at, | |
| } | |
| : previous, | |
| ) | |
| } | |
| }) | |
| socket.addEventListener('close', () => { | |
| setConnectionState('disconnected') | |
| }) | |
| socket.addEventListener('error', () => { | |
| setConnectionState('error') | |
| }) | |
| }, []) | |
| const bootSession = useCallback( | |
| async (restart = false) => { | |
| try { | |
| setError('') | |
| const payload = await createOrAttachSession(jobId, { restart }) | |
| setSession(payload.session) | |
| setBuffer(payload.buffer || '') | |
| attachSocket(payload.session.id) | |
| } catch (caughtError) { | |
| setError(caughtError.message) | |
| setConnectionState('error') | |
| } | |
| }, | |
| [attachSocket, jobId], | |
| ) | |
| useEffect(() => { | |
| const timeoutId = window.setTimeout(() => { | |
| void bootSession(false) | |
| }, 0) | |
| return () => { | |
| window.clearTimeout(timeoutId) | |
| if (socketRef.current) { | |
| socketRef.current.close() | |
| } | |
| } | |
| }, [bootSession]) | |
| const restart = useCallback(() => bootSession(true), [bootSession]) | |
| const stop = useCallback(async () => { | |
| if (!session?.id) { | |
| return | |
| } | |
| try { | |
| await stopTerminalSession(session.id) | |
| } catch (caughtError) { | |
| setError(caughtError.message) | |
| } | |
| }, [session]) | |
| const sendInput = useCallback( | |
| async (value, appendNewline = true) => { | |
| if (!session?.id || !value.trim()) { | |
| return | |
| } | |
| try { | |
| await sendTerminalInput(session.id, value, appendNewline) | |
| } catch (caughtError) { | |
| setError(caughtError.message) | |
| } | |
| }, | |
| [session], | |
| ) | |
| const resize = useCallback( | |
| async (cols, rows) => { | |
| if (!session?.id) { | |
| return | |
| } | |
| const previous = resizeRef.current | |
| if (previous.cols === cols && previous.rows === rows) { | |
| return | |
| } | |
| resizeRef.current = { cols, rows } | |
| try { | |
| await resizeTerminalSession(session.id, cols, rows) | |
| } catch { | |
| // Ignore resize errors so rendering stays responsive. | |
| } | |
| }, | |
| [session], | |
| ) | |
| return { | |
| buffer, | |
| connectionState, | |
| error, | |
| lastOutputAt, | |
| restart, | |
| resize, | |
| sendInput, | |
| session, | |
| start: () => bootSession(false), | |
| stop, | |
| } | |
| } | |