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

257 lines 10.5 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); // Mode states const [isModelSelectionMode, setIsModelSelectionMode] = useState(false); const [isProviderSelectionMode, setIsProviderSelectionMode] = useState(false); const [isModelDatabaseMode, setIsModelDatabaseMode] = useState(false); const [isConfigWizardMode, setIsConfigWizardMode] = useState(false); const [isMcpWizardMode, setIsMcpWizardMode] = useState(false); const [isCheckpointLoadMode, setIsCheckpointLoadMode] = useState(false); const [isExplorerMode, setIsExplorerMode] = useState(false); const [isIdeSelectionMode, setIsIdeSelectionMode] = useState(false); const [isVscodeEnabled, setIsVscodeEnabled] = useState(false); const [isSchedulerMode, setIsSchedulerMode] = useState(false); const [checkpointLoadData, setCheckpointLoadData] = useState(null); const [isToolConfirmationMode, setIsToolConfirmationMode] = useState(false); const [isToolExecuting, setIsToolExecuting] = useState(false); // 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, isModelSelectionMode, isProviderSelectionMode, isModelDatabaseMode, isConfigWizardMode, isMcpWizardMode, isCheckpointLoadMode, isExplorerMode, isIdeSelectionMode, isVscodeEnabled, isSchedulerMode, checkpointLoadData, isToolConfirmationMode, isToolExecuting, 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, setIsModelSelectionMode, setIsProviderSelectionMode, setIsModelDatabaseMode, setIsConfigWizardMode, setIsMcpWizardMode, setIsCheckpointLoadMode, setIsExplorerMode, setIsIdeSelectionMode, setIsVscodeEnabled, setIsSchedulerMode, setCheckpointLoadData, setIsToolConfirmationMode, setIsToolExecuting, setIsQuestionMode, setPendingQuestion, setDevelopmentMode, setContextPercentUsed, setContextLimit, setPendingToolCalls, setCurrentToolIndex, setCompletedToolResults, setCurrentConversationContext, setChatComponents, liveComponent, setLiveComponent, // Utilities addToChatQueue, getMessageTokens, updateMessages, resetToolConfirmationState, }; } //# sourceMappingURL=useAppState.js.map