UNPKG

@hhoangphuoc/escape-room-cli

Version:

A CLI for playing AI-generated escape room games. Install globally with: npm install -g @hhoangphuoc/escape-room-cli

335 lines (334 loc) 16.3 kB
// escape-room-cli/source/handlers/commandProcessor.ts import { handleHelpCommand, handleLookCommand, handleInspectCommand, handleGuessCommand, handlePasswordCommand, handleHintCommand, handleNewGameCommand, handleLeaderboardCommand, // handleLeaderboardMeCommand, handleLogoutCommand, handleLoginCommand, handleEndSessionCommand, handleNaturalLanguageCommand, handleInstructionsCommand, handleUsageCommand, handleCostCommand, // Simplified cost monitoring - removed handlePricingCommand, handleCompareCommand, handleEstimateCommand } from './commandHandlers.js'; import { displayResponse } from '../utils/responseDisplay.js'; // const REASONING_HOTKEY_MAX = 9; // let reasoningHotkeyCursor = 1; // const getNextReasoningHotkey = () => { // const current = reasoningHotkeyCursor; // reasoningHotkeyCursor = reasoningHotkeyCursor % REASONING_HOTKEY_MAX + 1; // return current; // }; const prepareHistoryItems = (items) => { return items.map((item) => { if (item.responseType === 'ai' && item.data && (item.data.reasoning?.summary || (item.data.reasoning?.steps && item.data.reasoning.steps.length > 0))) { // const hotkey = getNextReasoningHotkey(); return { ...item, // reasoningHotkey: hotkey, data: { ...item.data, // reasoningExpanded: false, }, }; } return item; }); }; // import { getApiUrl } from './apiConfig.js'; // Centralized processor for all commands export const handleCommand = async (props) => { const { command, auth, game, setHistory, setIsLoading, setLoadingMessage, setShowHistory, setShowModelSelector, setShowInstructions, setShowLeaderboard, setShowCostDashboard, setShowCostMonitor, selectedModel, conversation } = props; const THINKING_MESSAGE = [ `🤔 Thinking with ${selectedModel.label}...`, `🍲 Crafting solutions in ${selectedModel.label} ...`, `✨ Sparkling your thoughts in ${selectedModel.label}...`, `🔍 Investigating the puzzles with ${selectedModel.label}...`, `🔥 My ${selectedModel.label} thinking is on fire...`, ]; if (command.startsWith('/')) { const parts = command.trim().split(/\s+/); const cmd = parts[0]?.toLowerCase(); const userContext = { userId: auth.userId, sessionToken: auth.sessionToken, userName: auth.userName, cliApiKey: auth.apiKey, hasAICapability: auth.apiKey ? true : false, }; const gameContext = { currentGameId: game.gameId, currentRoomName: game.roomName, currentRoomBackground: game.roomBackground, currentGameMode: game.gameMode, totalRooms: game.totalRooms, unlockedObjects: game.unlockedObjects, currentRoomObjects: game.currentRoomObjects, }; let response; switch (cmd) { case '/help': response = handleHelpCommand(userContext, gameContext); break; case '/look': response = await handleLookCommand(userContext); if (response.success && response.roomData) game.setGameFromLook(response.roomData); break; case '/inspect': const objectName = parts.slice(1).join(' '); response = await handleInspectCommand(objectName, userContext); break; case '/guess': const guessObjectName = parts.slice(1, -1).join(' '); const answer = parts[parts.length - 1]; response = await handleGuessCommand(guessObjectName, answer, userContext); if (response.success && response.objectData?.unlocked) { game.unlockObject(response.objectData.name); } break; case '/password': const password = parts.slice(1).join(' '); response = await handlePasswordCommand(password, userContext); if (response.success && response.gameResult?.gameCompleted) { game.setGameCompleted(); // Persist conversation to Firebase when game is completed if (conversation && conversation.currentConversation && conversation.persistConversation && game.gameId) { try { const persisted = await conversation.persistConversation(game.gameId); if (persisted) { } else { console.warn('Failed to save conversation history'); } } catch (error) { console.error('Failed to persist conversation on game completion:', error); } } } else if (response.success && response.gameResult?.escaped) { game.fetchGameState(); } break; case '/hint': response = await handleHintCommand(userContext); break; case '/newgame': const mode = parts[1]?.toLowerCase() || 'single-room'; setIsLoading(true); setLoadingMessage(` 🍲 Cooking up an AI Escape Room... Stay tuned!`); response = await handleNewGameCommand(mode, userContext); if (response.success && response.gameData) { game.setGame(response.gameData); // Initialize conversation for new game if (conversation && conversation.createNewConversation && response.gameData.id) { try { const sessionId = `session_${Date.now()}`; conversation.createNewConversation(String(response.gameData.id), sessionId, response.gameData.mode || 'unknown'); // Add initial system message for game start setTimeout(async () => { if (conversation.addEntry) { await conversation.addEntry({ type: 'system_message', content: `New game started: ${response.gameData.name} (${response.gameData.mode})`, metadata: { requestType: 'system', gameId: String(response.gameData.id), roomId: response.gameData.name, }, }); } }, 100); // Small delay to ensure conversation is fully initialized } catch (error) { console.error('Failed to initialize conversation for new game:', error); } } } setIsLoading(false); break; case '/leaderboard': const leaderboardMode = parts[1]?.toLowerCase() || 'time'; response = await handleLeaderboardCommand(userContext, leaderboardMode); if (response.success && response.leaderboardData) { setShowLeaderboard(response.leaderboardData); return; } break; // case '/leaderboard/me': // response = await handleLeaderboardMeCommand(userContext); // if (response.success && response.leaderboardData) { // setShowLeaderboard(response.leaderboardData); // return; // } // break; case '/instructions': response = handleInstructionsCommand(userContext, gameContext); if (response.success) { setShowInstructions(true); return; } break; case '/logout': response = handleLogoutCommand(); auth.logout(); game.resetGame(); break; case '/end-session': response = await handleEndSessionCommand(userContext, gameContext); if (response.success) { // reset session costs client-side auth.resetSessionCosts && auth.resetSessionCosts(); // Persist conversation to Firebase if available if (conversation && conversation.currentConversation && conversation.persistConversation && game.gameId) { try { const persisted = await conversation.persistConversation(game.gameId); if (persisted) { response.message += ' Conversation history saved.'; } else { response.message += ' (Warning: Failed to save conversation history)'; } } catch (error) { response.message += ' (Warning: Failed to save conversation history)'; } } } break; case '/login': setIsLoading(true); setLoadingMessage('Attempting to login...'); response = await handleLoginCommand(); if (response.success && response.userData) { auth.login(response.userData); game.fetchGameState(); } setIsLoading(false); break; case '/register': response = { success: false, message: "To register, please restart the application without a saved session, or use CLI flags." }; break; case '/history': setShowHistory(true); return; case '/model': if (userContext.hasAICapability) { // Show model selector overlay and stop further processing setShowModelSelector(true); return; } else { response = { success: false, message: 'AI features not available. Please /login and use your API key to enable.' }; } break; case '/usage': response = await handleUsageCommand(userContext, gameContext); if (response.success && response.showUsageDashboard && response.usageData) { setShowCostDashboard(response.usageData); return; } break; case '/cost': response = await handleCostCommand(userContext, gameContext); if (response.success && response.showCostMonitor && response.costData) { setShowCostMonitor(response.costData); return; } break; // Removed /pricing, /compare, /estimate commands - simplified cost monitoring default: response = { success: false, message: `Unknown command: ${cmd}` }; } if (response) { const displayItems = prepareHistoryItems(displayResponse(response)); setHistory((prev) => [...prev, ...displayItems]); // Track conversation if available if (conversation && conversation.addEntry && game.gameId) { try { // Add user command entry await conversation.addEntry({ type: 'user_input', content: command, metadata: { requestType: 'command', gameId: game.gameId, roomId: game.currentRoomName, }, }); // Add system response entry if (response.message) { await conversation.addEntry({ type: 'agent_response', content: response.message, metadata: { requestType: 'command', gameId: game.gameId, roomId: game.currentRoomName, }, }); } } catch (error) { console.error('Failed to track command conversation entry:', error); } } } } else { // Natural language setIsLoading(true); // setLoadingMessage(`🤔 Thinking with ${selectedModel.label}...`); setLoadingMessage(THINKING_MESSAGE[Math.floor(Math.random() * THINKING_MESSAGE.length)]); const userContext = { userId: auth.userId, sessionToken: auth.sessionToken, userName: auth.userName, cliApiKey: auth.apiKey, hasAICapability: auth.apiKey ? true : false, }; const response = await handleNaturalLanguageCommand(command, userContext, selectedModel); if (response) { const displayItems = prepareHistoryItems(displayResponse(response)); setHistory((prev) => [...prev, ...displayItems]); // Track natural language conversation if (conversation && conversation.addEntry && game.gameId) { try { // Add user input entry await conversation.addEntry({ type: 'user_input', content: command, metadata: { model: selectedModel.value, requestType: 'natural_language', gameId: game.gameId, roomId: game.currentRoomName, }, }); // Add AI response entry if (response.message) { const totalTokens = response.usage?.tokens ?? ((response.usage?.inputTokens || 0) + (response.usage?.outputTokens || 0)); await conversation.addEntry({ type: 'agent_response', content: response.message, tokenUsage: response.usage ? { inputTokens: response.usage.inputTokens ?? 0, outputTokens: response.usage.outputTokens ?? 0, // reasoningTokens: response.usage.reasoningTokens, totalTokens, } : undefined, metadata: { model: response.usage?.model || selectedModel.value, requestType: 'natural_language', gameId: game.gameId, roomId: game.currentRoomName, cost: response.usage?.cost, }, // reasoningProcess: response.reasoning?.steps?.length // ? response.reasoning.steps.join('\n') // : response.reasoning?.summary, }); } } catch (error) { console.error('Failed to track natural language conversation entry:', error); } } } setIsLoading(false); } };