Terminal.jsx
1 import { 2 useState, 3 useRef, 4 useEffect 5 } from 'react'; 6 import PropTypes from 'prop-types'; 7 8 import { 9 handleMkdirCommand, 10 handleListCommand, 11 handleCdCommand, 12 handleTreeCommand, 13 handleRmCommand 14 } from './terminalUtils'; 15 import './Terminal.css'; 16 import TerminalPrompt from './TerminalPrompt'; 17 import TerminalOutput from './TerminalOutput'; 18 import TerminalInput from './TerminalInput'; 19 20 const Terminal = ({ onOpenNewChat }) => { 21 22 const [username] = useState(import.meta.env.VITE_USERNAME); // Set the username 23 const [port] = useState(parseInt(import.meta.env.VITE_PORT)); // Set the port 24 const [currentPath, setCurrentPath] = useState('home'); // Set the current path 25 const [commandHistory, setCommandHistory] = useState([]); // History of entered commands and their outputs 26 const [inputValue, setInputValue] = useState(''); // Current input value 27 const [inputHistory, setInputHistory] = useState([]); // History of entered commands for hotkeys 28 const [inputHistoryIndex, setInputHistoryIndex] = useState(-1); // Track input history index 29 30 const terminalRef = useRef(null); 31 const inputRef = useRef(null); 32 33 // Autoscroll on Terminal div overflow 34 useEffect(() => { 35 inputRef.current.focus(); 36 37 // Scroll to the bottom of the terminal 38 const terminalElement = terminalRef.current; 39 if (terminalElement) { 40 terminalElement.scrollTop = terminalElement.scrollHeight; 41 } 42 }, [commandHistory]); 43 44 useEffect(() => { 45 inputRef.current.focus(); 46 }, []); 47 48 const handleCommandInput = (command) => { 49 const [cmd, arg] = command.trim().split(' '); 50 const promptline = `${username}@${port} ${currentPath.split('/').pop()}`; 51 let output; 52 53 switch (cmd) { 54 case 'mkdir': 55 try { 56 output = handleMkdirCommand(currentPath, arg) + '\n'; 57 } catch (e) { 58 output = 'mkdir: ' + e.message + '\n'; 59 } 60 break; 61 case 'rm': 62 try { 63 output = handleRmCommand(currentPath, arg) + '\n'; 64 } catch (e) { 65 output = 'rm: ' + e.message + '\n'; 66 } 67 break; 68 case 'ls': 69 try { 70 output = handleListCommand(currentPath, arg) + '\n'; 71 } catch (e) { 72 output = 'ls: ' + e.message + '\n'; 73 } 74 break; 75 case 'cd': 76 try { 77 setCurrentPath(handleCdCommand(currentPath, arg)); 78 output = ''; 79 } catch (e) { 80 output = 'cd: ' + e.message + '\n'; 81 } 82 break; 83 case 'tree': 84 output = handleTreeCommand(currentPath) + '\n'; 85 break; 86 case 'run': 87 if (arg === 'new_chat.sh') { 88 onOpenNewChat(); // Call the prop function 89 output = 'Opening new chat interface...\n'; 90 } else { 91 output = 'Invalid command: run ' + arg + '\n'; 92 } 93 break; 94 case 'clear': 95 setCommandHistory([]); 96 setInputHistory(prevInputHistory => [command, ...prevInputHistory]); 97 setInputValue(''); 98 return; 99 case 'pwd': 100 output = currentPath + '\n'; 101 break; 102 case 'help': 103 output = ('help command text') // TODO 104 break; 105 default: 106 output = `Command not found: ${cmd}\n`; 107 } 108 109 setCommandHistory(prevHistory => [...prevHistory, { promptline, command, output }]); 110 setInputValue(''); 111 setInputHistory(prevInputHistory => [command, ...prevInputHistory]); 112 setInputHistoryIndex(-1); // Reset input history index 113 }; 114 115 const handleKeyDown = (e) => { 116 e.preventDefault(); 117 const newIndex = e.key === 'ArrowUp' ? inputHistoryIndex + 1 : inputHistoryIndex - 1; 118 if (newIndex >= 0 && newIndex < inputHistory.length) { 119 setInputValue(inputHistory[newIndex]); 120 setInputHistoryIndex(newIndex); 121 } else { 122 setInputValue(''); 123 setInputHistoryIndex(-1); 124 } 125 }; 126 127 return ( 128 <div className="terminal" ref={terminalRef}> 129 <TerminalOutput commandHistory={commandHistory} /> 130 <div className="input-line"> 131 <TerminalPrompt username={username} port={port} currentPath={currentPath} /> 132 <div className="input-spacer"></div> 133 <TerminalInput 134 inputRef={inputRef} 135 inputValue={inputValue} 136 setInputValue={setInputValue} 137 handleCommandInput={handleCommandInput} 138 handleKeyDown={handleKeyDown} 139 /> 140 </div> 141 </div> 142 ); 143 }; 144 145 Terminal.propTypes = { 146 onOpenNewChat: PropTypes.func.isRequired 147 } 148 149 export default Terminal;