import { useState, useEffect, useRef } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { Link } from "wouter"; import { useLabTerminal } from "@/hooks/use-lab-terminal"; import { ArrowLeft, Terminal as TerminalIcon, Copy, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Select, SelectTrigger, SelectValue, SelectContent, SelectItem } from "@/components/ui/select"; import { useToast } from "@/hooks/use-toast"; interface TerminalLine { type: 'input' | 'output' | 'error' | 'system'; content: string; timestamp: number; } export default function Terminal() { const [lines, setLines] = useState([ { type: 'system', content: '▸ AeThex Terminal v4.2 - Type "help" for commands', timestamp: Date.now(), }, ]); const [cliCommand, setCliCommand] = useState("build"); const [cliStatus, setCliStatus] = useState<"idle" | "running" | "done" | "error">("idle"); const [cliLabel, setCliLabel] = useState(""); const eventSourceRef = useRef(null); const currentRunId = useRef(null); const { toast } = useToast(); useEffect(() => { return () => { eventSourceRef.current?.close(); }; }, []); const [input, setInput] = useState(""); const [commandHistory, setCommandHistory] = useState([]); const [historyIndex, setHistoryIndex] = useState(-1); const terminalEndRef = useRef(null); const { executionHistory, executeCode, isExecuting } = useLabTerminal(); const scrollToBottom = () => { terminalEndRef.current?.scrollIntoView({ behavior: "smooth" }); }; useEffect(() => { scrollToBottom(); }, [lines]); // Show last execution in terminal useEffect(() => { if (executionHistory.length > 0) { const latest = executionHistory[0]; setLines((prev) => [ ...prev, { type: 'input', content: `> run "${latest.code.split('\n')[0]}..."`, timestamp: latest.timestamp, }, { type: latest.status === 'error' ? 'error' : 'output', content: latest.output, timestamp: latest.timestamp, }, ]); } }, [executionHistory]); const processCommand = (cmd: string) => { const trimmed = cmd.trim(); if (!trimmed) return; setLines((prev) => [ ...prev, { type: 'input', content: `> ${trimmed}`, timestamp: Date.now() }, ]); const parts = trimmed.split(' '); const command = parts[0].toLowerCase(); switch (command) { case 'help': setLines((prev) => [ ...prev, { type: 'system', content: `Available commands: help - Show this help message clear - Clear terminal execute - Execute JavaScript code run - Run code from Lab history - Show command history exit - Exit terminal`, timestamp: Date.now(), }, ]); break; case 'clear': setLines([]); break; case 'history': setLines((prev) => [ ...prev, { type: 'output', content: commandHistory.join('\n') || '(empty)', timestamp: Date.now(), }, ]); break; case 'execute': const code = parts.slice(1).join(' '); if (code) { executeCode(code, 'javascript'); } else { setLines((prev) => [ ...prev, { type: 'error', content: 'Usage: execute ', timestamp: Date.now(), }, ]); } break; case 'run': setLines((prev) => [ ...prev, { type: 'output', content: '▸ Open Lab to run files', timestamp: Date.now(), }, ]); break; case 'exit': setLines((prev) => [ ...prev, { type: 'system', content: '▸ Type "help" to see available commands', timestamp: Date.now(), }, ]); break; default: setLines((prev) => [ ...prev, { type: 'error', content: `Command not found: ${command}. Type "help" for available commands.`, timestamp: Date.now(), }, ]); } setCommandHistory((prev) => [...prev, trimmed]); setInput(""); setHistoryIndex(-1); }; const appendCliLine = (type: TerminalLine['type'], content: string) => { setLines((prev) => [...prev, { type, content, timestamp: Date.now() }]); }; const stopCli = async () => { if (!currentRunId.current) return; try { await fetch(`/api/admin/cli/stop/${currentRunId.current}`, { method: "POST", credentials: "include" }); } catch (_) { // ignore } eventSourceRef.current?.close(); eventSourceRef.current = null; currentRunId.current = null; setCliStatus("idle"); }; const startCli = async () => { if (cliStatus === "running") return; setCliStatus("running"); appendCliLine('system', `▸ Running ${cliCommand}...`); toast({ title: "CLI", description: `Started ${cliCommand}`, variant: "default" }); try { const res = await fetch('/api/admin/cli/start', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify({ command: cliCommand }) }); if (!res.ok) { const text = await res.text(); appendCliLine('error', `Start failed: ${text || res.status}`); setCliStatus("error"); toast({ title: "CLI Error", description: `Failed to start ${cliCommand}`, variant: "destructive" }); return; } const data = await res.json(); currentRunId.current = data.id; setCliLabel(data.label); const es = new EventSource(`/api/admin/cli/stream/${data.id}`, { withCredentials: true } as any); eventSourceRef.current = es; es.addEventListener('message', (evt) => { appendCliLine('output', evt.data); }); es.addEventListener('error', (evt) => { appendCliLine('error', 'Stream error'); setCliStatus("error"); toast({ title: "CLI Error", description: `Stream error for ${cliCommand}`, variant: "destructive" }); es.close(); }); es.addEventListener('done', (evt) => { const status = evt.data === 'success' ? 'done' : 'error'; setCliStatus(status as any); appendCliLine(status === 'done' ? 'system' : 'error', `▸ ${cliLabel || cliCommand} ${status}`); toast({ title: status === 'done' ? "CLI Success" : "CLI Failed", description: `${cliLabel || cliCommand} ${status}` }); es.close(); currentRunId.current = null; }); } catch (err) { appendCliLine('error', 'Failed to start CLI command'); setCliStatus("error"); toast({ title: "CLI Error", description: `Failed to start ${cliCommand}`, variant: "destructive" }); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { processCommand(input); } else if (e.key === 'ArrowUp') { e.preventDefault(); const newIndex = historyIndex + 1; if (newIndex < commandHistory.length) { setHistoryIndex(newIndex); setInput(commandHistory[commandHistory.length - 1 - newIndex]); } } else if (e.key === 'ArrowDown') { e.preventDefault(); if (historyIndex > 0) { const newIndex = historyIndex - 1; setHistoryIndex(newIndex); setInput(commandHistory[commandHistory.length - 1 - newIndex]); } else if (historyIndex === 0) { setHistoryIndex(-1); setInput(""); } } }; return (
{/* Header */}
AeThex Terminal v4.2 [ ONLINE ]
{/* Terminal Output */} {/* Admin CLI runner */}
AeThex CLI
Status: {cliStatus.toUpperCase()}
{lines.map((line, i) => ( {line.content.split('\n').map((part, idx) => (
{part}
))}
))}
{/* Input Bar */}
setInput(e.target.value)} onKeyDown={handleKeyDown} placeholder="Type command... (help for list)" className="flex-1 bg-[#0a0a0c] border-0 text-[#a9b7c6] placeholder-[#555] focus:ring-0 focus:outline-none font-mono text-xs" disabled={isExecuting} autoFocus /> {isExecuting && ( executing... )}
); }