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

266 lines 11 kB
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { loadPreferences } from '../config/preferences.js'; import { defaultTheme } from '../config/themes.js'; import { createTokenizer } from '../tokenization/index.js'; import { BoundedMap } from '../utils/bounded-map.js'; export function useAppState() { // Initialize theme and title shape from preferences const preferences = loadPreferences(); const initialTheme = preferences.selectedTheme || defaultTheme; const initialTitleShape = preferences.titleShape || 'pill'; const [client, setClient] = useState(null); const [messages, setMessages] = useState([]); const [displayMessages, setDisplayMessages] = useState([]); const [messageTokenCache, setMessageTokenCache] = useState(new BoundedMap({ maxSize: 1000, // No TTL - cache is session-based and cleared on app restart })); const [currentModel, setCurrentModel] = useState(''); const [currentProvider, setCurrentProvider] = useState('openai-compatible'); const [currentTheme, setCurrentTheme] = useState(initialTheme); const [currentTitleShape, setCurrentTitleShape] = useState(initialTitleShape); const [toolManager, setToolManager] = useState(null); const [customCommandLoader, setCustomCommandLoader] = useState(null); const [customCommandExecutor, setCustomCommandExecutor] = useState(null); const [customCommandCache, setCustomCommandCache] = useState(new Map()); const [startChat, setStartChat] = useState(false); const [mcpInitialized, setMcpInitialized] = useState(false); const [updateInfo, setUpdateInfo] = useState(null); // Connection status states const [mcpServersStatus, setMcpServersStatus] = useState([]); const [lspServersStatus, setLspServersStatus] = useState([]); // Initialization status states const [preferencesLoaded, setPreferencesLoaded] = useState(false); const [customCommandsCount, setCustomCommandsCount] = useState(0); // Cancelling indicator state const [isCancelling, setIsCancelling] = useState(false); const [isConversationComplete, setIsConversationComplete] = useState(false); const [isSettingsMode, setIsSettingsMode] = useState(false); // Cancellation state const [abortController, setAbortController] = useState(null); // Unified modal/mode state - replaces 11 individual boolean states const [activeMode, setActiveMode] = useState(null); const [isVscodeEnabled, setIsVscodeEnabled] = useState(false); const [checkpointLoadData, setCheckpointLoadData] = useState(null); const [showAllSessions, setShowAllSessions] = useState(false); const [currentSessionId, setCurrentSessionId] = useState(null); const [isToolConfirmationMode, setIsToolConfirmationMode] = useState(false); const [isToolExecuting, setIsToolExecuting] = useState(false); // Compact tool display state const [compactToolDisplay, setCompactToolDisplay] = useState(true); // Ref keeps current value accessible to long-running async loops const compactToolDisplayRef = useRef(true); compactToolDisplayRef.current = compactToolDisplay; const [compactToolCounts, setCompactToolCounts] = useState(null); // Mutable ref for the compact counts accumulator - shared between // the async conversation loop and the toggle handler const compactToolCountsRef = useRef({}); // Question mode state (ask_question tool) const [isQuestionMode, setIsQuestionMode] = useState(false); const [pendingQuestion, setPendingQuestion] = useState(null); // Development mode state const [developmentMode, setDevelopmentMode] = useState('normal'); // Context usage state const [contextPercentUsed, setContextPercentUsed] = useState(null); const [contextLimit, setContextLimit] = useState(null); // Tool confirmation state const [pendingToolCalls, setPendingToolCalls] = useState([]); const [currentToolIndex, setCurrentToolIndex] = useState(0); const [completedToolResults, setCompletedToolResults] = useState([]); const [currentConversationContext, setCurrentConversationContext] = useState(null); // Chat queue for components const [chatComponents, setChatComponents] = useState([]); // Live component that renders outside Static for real-time updates (e.g., BashProgress) const [liveComponent, setLiveComponent] = useState(null); // Use ref for component key counter to avoid stale closure issues // State updates are async/batched, but ref updates are synchronous // This prevents duplicate keys when addToChatQueue is called rapidly const componentKeyCounterRef = useRef(0); // Get the next unique component key - synchronous to prevent duplicates const getNextComponentKey = useCallback(() => { componentKeyCounterRef.current += 1; return componentKeyCounterRef.current; }, []); // Helper function to add components to the chat queue with stable keys const addToChatQueue = useCallback((component) => { const newCounter = getNextComponentKey(); let componentWithKey = component; if (React.isValidElement(component) && !component.key) { componentWithKey = React.cloneElement(component, { key: `chat-component-${newCounter}`, }); } setChatComponents(prevComponents => [ ...prevComponents, componentWithKey, ]); }, [getNextComponentKey]); // Create tokenizer based on current provider and model const tokenizer = useMemo(() => { if (currentProvider && currentModel) { return createTokenizer(currentProvider, currentModel); } // Fallback to simple char/4 heuristic if provider/model not set return createTokenizer('', ''); }, [currentProvider, currentModel]); // Cleanup tokenizer resources when it changes useEffect(() => { return () => { if (tokenizer.free) { tokenizer.free(); } }; }, [tokenizer]); // Helper function for token calculation with caching const getMessageTokens = useCallback((message) => { const cacheKey = (message.content || '') + message.role + currentModel; const cachedTokens = messageTokenCache.get(cacheKey); if (cachedTokens !== undefined) { return cachedTokens; } const tokens = tokenizer.countTokens(message); // Defer cache update to avoid "Cannot update a component while rendering" error // This can happen when components call getMessageTokens during their render queueMicrotask(() => { setMessageTokenCache(prev => { const newCache = new BoundedMap({ maxSize: 1000, }); // Copy existing entries for (const [k, v] of prev.entries()) { newCache.set(k, v); } // Add new entry newCache.set(cacheKey, tokens); return newCache; }); }); return tokens; }, [messageTokenCache, tokenizer, currentModel]); // Message updater - no limits, display all messages const updateMessages = useCallback((newMessages) => { setMessages(newMessages); setDisplayMessages(newMessages); }, []); // Reset tool confirmation state const resetToolConfirmationState = () => { setIsToolConfirmationMode(false); setIsToolExecuting(false); setPendingToolCalls([]); setCurrentToolIndex(0); setCompletedToolResults([]); setCurrentConversationContext(null); }; return { // State client, messages, displayMessages, messageTokenCache, currentModel, currentProvider, currentTheme, currentTitleShape, toolManager, customCommandLoader, customCommandExecutor, customCommandCache, startChat, mcpInitialized, updateInfo, mcpServersStatus, lspServersStatus, preferencesLoaded, customCommandsCount, isCancelling, isConversationComplete, isSettingsMode, abortController, // Unified mode state activeMode, setActiveMode, // Derived mode booleans (read-only convenience) isModelSelectionMode: activeMode === 'model', isProviderSelectionMode: activeMode === 'provider', isModelDatabaseMode: activeMode === 'modelDatabase', isConfigWizardMode: activeMode === 'configWizard', isMcpWizardMode: activeMode === 'mcpWizard', isCheckpointLoadMode: activeMode === 'checkpointLoad', isExplorerMode: activeMode === 'explorer', isIdeSelectionMode: activeMode === 'ideSelection', isSchedulerMode: activeMode === 'scheduler', isSessionSelectorMode: activeMode === 'sessionSelector', isVscodeEnabled, checkpointLoadData, showAllSessions, currentSessionId, isToolConfirmationMode, isToolExecuting, compactToolDisplay, compactToolDisplayRef, compactToolCounts, compactToolCountsRef, isQuestionMode, pendingQuestion, developmentMode, contextPercentUsed, contextLimit, pendingToolCalls, currentToolIndex, completedToolResults, currentConversationContext, chatComponents, getNextComponentKey, tokenizer, // Setters setClient, setMessages, setDisplayMessages, setMessageTokenCache, setCurrentModel, setCurrentProvider, setCurrentTheme, setCurrentTitleShape, setToolManager, setCustomCommandLoader, setCustomCommandExecutor, setCustomCommandCache, setStartChat, setMcpInitialized, setUpdateInfo, setMcpServersStatus, setLspServersStatus, setPreferencesLoaded, setCustomCommandsCount, setIsCancelling, setIsConversationComplete, setIsSettingsMode, setAbortController, setIsVscodeEnabled, setCheckpointLoadData, setShowAllSessions, setCurrentSessionId, setIsToolConfirmationMode, setIsToolExecuting, setCompactToolDisplay, setCompactToolCounts, setIsQuestionMode, setPendingQuestion, setDevelopmentMode, setContextPercentUsed, setContextLimit, setPendingToolCalls, setCurrentToolIndex, setCompletedToolResults, setCurrentConversationContext, setChatComponents, liveComponent, setLiveComponent, // Utilities addToChatQueue, getMessageTokens, updateMessages, resetToolConfirmationState, }; } //# sourceMappingURL=useAppState.js.map