@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
JavaScript
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