UNPKG

@nanocollective/nanocoder

Version:

A local-first CLI coding agent that brings the power of agentic coding tools like Claude Code and Gemini CLI to local models or controlled APIs like OpenRouter

315 lines 14.4 kB
import { jsx as _jsx } from "react/jsx-runtime"; import React from 'react'; import { createClearMessagesHandler, handleMessageSubmission, } from '../app/utils/app-util.js'; import { ErrorMessage, SuccessMessage, WarningMessage, } from '../components/message-box.js'; import Status from '../components/status.js'; import { getAppConfig } from '../config/index.js'; import { setCurrentMode as setCurrentModeContext } from '../context/mode-context.js'; import { getModelContextLimit } from '../models/index.js'; import { CheckpointManager } from '../services/checkpoint-manager.js'; import { sessionManager } from '../session/session-manager.js'; import { createTokenizer } from '../tokenization/index.js'; import { calculateTokenBreakdown } from '../usage/calculator.js'; import { autoCompactSessionOverrides } from '../utils/auto-compact.js'; import { getLogger } from '../utils/logging/index.js'; import { processPromptTemplate } from '../utils/prompt-processor.js'; /** * Consolidates all app handler setup into a single hook */ export function useAppHandlers(props) { const logger = getLogger(); // Clear messages handler const clearMessages = React.useMemo(() => async () => { const baseClear = createClearMessagesHandler(props.updateMessages, props.client); await baseClear(); props.setCurrentSessionId(null); }, [props.updateMessages, props.client, props.setCurrentSessionId, props]); // Cancel handler const handleCancel = React.useCallback(() => { if (props.abortController) { logger.info('Cancelling current operation', { operation: 'user_cancellation', hasAbortController: !!props.abortController, }); props.setIsCancelling(true); props.abortController.abort(); } else { logger.debug('Cancel requested but no active operation to cancel'); } }, [props.abortController, props.setIsCancelling, logger, props]); // Toggle development mode handler const handleToggleDevelopmentMode = React.useCallback(() => { props.setDevelopmentMode(currentMode => { // Don't allow toggling out of scheduler mode via Shift+Tab if (currentMode === 'scheduler') return currentMode; const modes = [ 'normal', 'auto-accept', 'plan', ]; const currentIndex = modes.indexOf(currentMode); const nextIndex = (currentIndex + 1) % modes.length; const nextMode = modes[nextIndex]; logger.info('Development mode toggled', { previousMode: currentMode, nextMode, modeIndex: nextIndex, totalModes: modes.length, }); // Sync global mode context for tool needsApproval logic setCurrentModeContext(nextMode); return nextMode; }); }, [props.setDevelopmentMode, logger, props]); // Show status handler const handleShowStatus = React.useCallback(async () => { logger.debug('Status display requested', { currentProvider: props.currentProvider, currentModel: props.currentModel, currentTheme: props.currentTheme, }); // Calculate context usage and auto-compact info let contextUsage; let autoCompactInfo; try { // Calculate context usage const contextLimit = await getModelContextLimit(props.currentModel); if (contextLimit && props.messages.length > 0) { const tokenizer = createTokenizer(props.currentProvider, props.currentModel); try { const systemPrompt = processPromptTemplate(); const systemMessage = { role: 'system', content: systemPrompt, }; const breakdown = calculateTokenBreakdown([systemMessage, ...props.messages], tokenizer, props.getMessageTokens); const percentUsed = (breakdown.total / contextLimit) * 100; contextUsage = { currentTokens: breakdown.total, contextLimit, percentUsed, }; } finally { if (tokenizer.free) { tokenizer.free(); } } } // Get auto-compact info const config = getAppConfig(); const autoCompactConfig = config.autoCompact; if (autoCompactConfig) { const enabled = autoCompactSessionOverrides.enabled !== null ? autoCompactSessionOverrides.enabled : autoCompactConfig.enabled; const threshold = autoCompactSessionOverrides.threshold !== null ? autoCompactSessionOverrides.threshold : autoCompactConfig.threshold; const mode = autoCompactSessionOverrides.mode !== null ? autoCompactSessionOverrides.mode : autoCompactConfig.mode; const hasOverrides = autoCompactSessionOverrides.enabled !== null || autoCompactSessionOverrides.threshold !== null || autoCompactSessionOverrides.mode !== null; autoCompactInfo = { enabled, threshold, mode, hasOverrides, }; } } catch (error) { logger.debug('Failed to calculate status info', { error }); // Continue without context usage/auto-compact info } props.addToChatQueue(_jsx(Status, { provider: props.currentProvider, model: props.currentModel, theme: props.currentTheme, updateInfo: props.updateInfo, mcpServersStatus: props.mcpServersStatus, lspServersStatus: props.lspServersStatus, preferencesLoaded: props.preferencesLoaded, customCommandsCount: props.customCommandsCount, contextUsage: contextUsage, autoCompactInfo: autoCompactInfo }, `status-${props.getNextComponentKey()}`)); }, [ props.currentProvider, props.currentModel, props.currentTheme, props.updateInfo, props.mcpServersStatus, props.lspServersStatus, props.preferencesLoaded, props.customCommandsCount, props.messages, props.getMessageTokens, props.addToChatQueue, props.getNextComponentKey, logger, props, ]); // Checkpoint select handler const handleCheckpointSelect = React.useCallback(async (checkpointName, createBackup) => { try { const manager = new CheckpointManager(); if (createBackup) { try { await manager.saveCheckpoint(`backup-${new Date().toISOString().replace(/[:.]/g, '-')}`, props.messages, props.currentProvider, props.currentModel); } catch (error) { props.addToChatQueue(_jsx(WarningMessage, { message: `Warning: Failed to create backup: ${error instanceof Error ? error.message : 'Unknown error'}`, hideBox: true }, `backup-warning-${props.getNextComponentKey()}`)); } } const checkpointData = await manager.loadCheckpoint(checkpointName, { validateIntegrity: true, }); await manager.restoreFiles(checkpointData); props.addToChatQueue(_jsx(SuccessMessage, { message: `✓ Checkpoint '${checkpointName}' restored successfully`, hideBox: true }, `restore-success-${props.getNextComponentKey()}`)); } catch (error) { props.addToChatQueue(_jsx(ErrorMessage, { message: `Failed to restore checkpoint: ${error instanceof Error ? error.message : 'Unknown error'}`, hideBox: true }, `restore-error-${props.getNextComponentKey()}`)); } finally { props.setActiveMode(null); props.setCheckpointLoadData(null); } }, [ props.messages, props.currentProvider, props.currentModel, props.setActiveMode, props.setCheckpointLoadData, props.addToChatQueue, props.getNextComponentKey, props, ]); // Checkpoint cancel handler const handleCheckpointCancel = React.useCallback(() => { props.setActiveMode(null); props.setCheckpointLoadData(null); }, [props.setActiveMode, props.setCheckpointLoadData, props]); // Enter checkpoint load mode handler const enterCheckpointLoadMode = React.useCallback((checkpoints, currentMessageCount) => { props.setCheckpointLoadData({ checkpoints, currentMessageCount }); props.setActiveMode('checkpointLoad'); }, [props.setCheckpointLoadData, props.setActiveMode, props]); // Enter session selector mode (for /resume with no args) const enterSessionSelectorMode = React.useCallback((showAll) => { props.setShowAllSessions(showAll ?? false); props.setActiveMode('sessionSelector'); }, [props.setShowAllSessions, props.setActiveMode, props]); // Load and apply a session (messages, provider, model) const applySession = React.useCallback((session) => { props.updateMessages(session.messages); props.setCurrentProvider(session.provider); props.setCurrentModel(session.model); props.setCurrentSessionId(session.id); props.addToChatQueue(_jsx(SuccessMessage, { message: `Resumed session: ${session.title}`, hideBox: true }, `resume-success-${Date.now()}`)); props.setActiveMode(null); }, [ props.updateMessages, props.setCurrentProvider, props.setCurrentModel, props.setCurrentSessionId, props.setActiveMode, props.addToChatQueue, props, ]); const handleSessionSelect = React.useCallback(async (sessionId) => { try { const session = await sessionManager.loadSession(sessionId); if (session) { applySession(session); } else { props.addToChatQueue(_jsx(ErrorMessage, { message: "Session not found", hideBox: true }, `resume-error-${Date.now()}`)); props.setActiveMode(null); } } catch (error) { props.addToChatQueue(_jsx(ErrorMessage, { message: `Failed to load session: ${error instanceof Error ? error.message : 'Unknown error'}`, hideBox: true }, `resume-error-${Date.now()}`)); props.setActiveMode(null); } }, [applySession, props.addToChatQueue, props.setActiveMode, props]); const handleSessionCancel = React.useCallback(() => { props.setActiveMode(null); }, [props.setActiveMode, props]); // Message submit handler const handleMessageSubmit = React.useCallback(async (message) => { // Reset conversation completion flag when starting a new message props.setIsConversationComplete(false); await handleMessageSubmission(message, { customCommandCache: props.customCommandCache, customCommandLoader: props.customCommandLoader, customCommandExecutor: props.customCommandExecutor, onClearMessages: clearMessages, onEnterModelSelectionMode: props.enterModelSelectionMode, onEnterProviderSelectionMode: props.enterProviderSelectionMode, onEnterModelDatabaseMode: props.enterModelDatabaseMode, onEnterConfigWizardMode: props.enterConfigWizardMode, onEnterSettingsMode: props.enterSettingsMode, onEnterMcpWizardMode: props.enterMcpWizardMode, onEnterExplorerMode: props.enterExplorerMode, onEnterIdeSelectionMode: props.enterIdeSelectionMode, onEnterSchedulerMode: props.enterSchedulerMode, onEnterCheckpointLoadMode: enterCheckpointLoadMode, onEnterSessionSelectorMode: enterSessionSelectorMode, onResumeSession: session => applySession(session), onShowStatus: handleShowStatus, onHandleChatMessage: props.handleChatMessage, onAddToChatQueue: props.addToChatQueue, setLiveComponent: props.setLiveComponent, setIsToolExecuting: props.setIsToolExecuting, onCommandComplete: () => props.setIsConversationComplete(true), getNextComponentKey: props.getNextComponentKey, setMessages: props.updateMessages, messages: props.messages, provider: props.currentProvider, model: props.currentModel, theme: props.currentTheme, updateInfo: props.updateInfo, getMessageTokens: props.getMessageTokens, }); }, [ props.setIsConversationComplete, props.customCommandCache, props.customCommandLoader, props.customCommandExecutor, props.enterModelSelectionMode, props.enterProviderSelectionMode, props.enterModelDatabaseMode, props.enterConfigWizardMode, props.enterSettingsMode, props.enterMcpWizardMode, props.enterExplorerMode, props.enterIdeSelectionMode, props.enterSchedulerMode, props.handleChatMessage, props.addToChatQueue, props.setLiveComponent, props.setIsToolExecuting, props.getNextComponentKey, props.updateMessages, props.messages, props.currentProvider, props.currentModel, props.currentTheme, props.updateInfo, props.getMessageTokens, clearMessages, enterCheckpointLoadMode, handleShowStatus, applySession, enterSessionSelectorMode, props, ]); return { clearMessages, handleCancel, handleToggleDevelopmentMode, handleShowStatus, handleCheckpointSelect, handleCheckpointCancel, enterCheckpointLoadMode, enterSessionSelectorMode, handleSessionSelect, handleSessionCancel, handleMessageSubmit, }; } //# sourceMappingURL=useAppHandlers.js.map