@hhoangphuoc/escape-room-cli
Version:
A CLI for playing AI-generated escape room games. Install globally with: npm install -g @hhoangphuoc/escape-room-cli
84 lines (83 loc) • 4.59 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
import { useState, useEffect } from 'react';
import { Box, Text, useInput, useStdin } from 'ink';
import { COMMANDS } from '../utils/constants.js';
import { applyTabCompletion, getCompletionDisplayInfo } from '../utils/tabCompletion.js';
const CommandInput = ({ value, onChange, onSubmit, mode = 'standard', currentRoomObjects = [], }) => {
const { isRawModeSupported } = useStdin();
const [cursorVisible, setCursorVisible] = useState(true);
const [showSuggestions, setShowSuggestions] = useState(false);
const [filteredCommands, setFilteredCommands] = useState({});
const [completionInfo, setCompletionInfo] = useState(null);
// Blink cursor effect
useEffect(() => {
if (!isRawModeSupported)
return;
const timer = setInterval(() => {
setCursorVisible(prev => !prev);
}, 500);
return () => clearInterval(timer);
}, [isRawModeSupported]);
// Update suggestions based on input and context
useEffect(() => {
const context = {
currentRoomObjects,
availableCommands: Object.keys(COMMANDS),
};
// Get completion info for display
const info = getCompletionDisplayInfo(value, context);
setCompletionInfo(info);
// Show suggestions when there are completions available
if (info && info.suggestions.length > 0) {
setShowSuggestions(true);
// Convert to the format expected by the existing display logic
const filtered = info.suggestions.reduce((acc, suggestion) => {
const cmd = suggestion.startsWith('/') ? suggestion : '/' + suggestion;
const isObjectName = info.title === 'Available Objects';
acc[cmd] = {
description: COMMANDS[cmd]?.description || (isObjectName ? `Room object: ${suggestion}` : `${info.title}: ${suggestion}`),
usage: COMMANDS[cmd]?.usage || suggestion,
};
return acc;
}, {});
setFilteredCommands(filtered);
}
else {
setShowSuggestions(false);
setFilteredCommands({});
}
}, [value, mode, currentRoomObjects]);
// Handle keyboard input only if raw mode is supported
useInput((input, key) => {
if (!isRawModeSupported)
return;
if (key.return) {
if (value.trim() !== '') {
onSubmit(value);
onChange('');
}
}
else if (key.tab) {
// Handle tab completion
const context = {
currentRoomObjects,
availableCommands: Object.keys(COMMANDS),
};
const completed = applyTabCompletion(value, context);
onChange(completed);
}
else if (key.backspace || key.delete) {
onChange(value.slice(0, -1));
}
else if (!key.ctrl && !key.meta) {
onChange(value + input);
}
});
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: "yellow", children: "> " }), _jsx(Text, { children: value }), isRawModeSupported ? (_jsx(Text, { color: "gray", children: cursorVisible ? '█' : ' ' })) : (_jsx(Text, { color: "red", children: "[Input disabled - run in interactive terminal]" }))] }), showSuggestions && Object.keys(filteredCommands).length > 0 && (_jsxs(Box, { flexDirection: "column", marginLeft: 2, marginTop: 1, borderStyle: "round", borderColor: "gray", paddingX: 1, children: [_jsxs(Box, { marginBottom: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: completionInfo?.title || 'Available Commands' }), _jsx(Box, { marginLeft: 2, children: _jsx(Text, { color: "gray", children: "(Press Tab to complete)" }) })] }), Object.entries(filteredCommands).map(([cmd, details]) => {
const isCurrentMatch = completionInfo?.currentMatch &&
(cmd.toLowerCase().startsWith(completionInfo.currentMatch.toLowerCase()) ||
details.usage.toLowerCase().startsWith(completionInfo.currentMatch.toLowerCase()));
return (_jsxs(Box, { marginBottom: 0, children: [_jsx(Text, { color: isCurrentMatch ? 'cyan' : 'white', bold: isCurrentMatch, children: details.usage.padEnd(30) }), _jsx(Text, { color: 'gray', children: details.description })] }, cmd));
})] }))] }));
};
export default CommandInput;