Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,15 @@
"ink-gradient": "^3.0.0",
"ink-select-input": "^6.2.0",
"ink-spinner": "^5.0.0",
"ink-text-input": "^6.0.0",
"meow": "^11.0.0",
"nanostores": "^1.0.1",
"ollama": "^0.5.17",
"react": "^19.0.0"
},
"devDependencies": {
"@sindresorhus/tsconfig": "^3.0.1",
"@types/ink-text-input": "^2.0.5",
"@types/node": "^24.3.0",
"@types/react": "^19.0.0",
"@vdemedes/prettier-config": "^2.0.1",
Expand Down
169 changes: 89 additions & 80 deletions source/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@ import {useModeHandlers} from './app/hooks/useModeHandlers.js';
import {useAppInitialization} from './app/hooks/useAppInitialization.js';
import {useDirectoryTrust} from './app/hooks/useDirectoryTrust.js';
import {
handleMessageSubmission,
createClearMessagesHandler,
handleMessageSubmission,
} from './app/utils/appUtils.js';

// Provide shared UI state to components
import {UIStateProvider} from './hooks/useUIState.js';

export default function App() {
// Use extracted hooks
const appState = useAppState();
Expand Down Expand Up @@ -233,88 +236,94 @@ export default function App() {

return (
<ThemeContext.Provider value={themeContextValue}>
<Box flexDirection="column" padding={1} width="100%">
<Box flexGrow={1} flexDirection="column" minHeight={0}>
<WelcomeMessage />
{appState.startChat && (
<ChatQueue
staticComponents={staticComponents}
queuedComponents={appState.chatComponents}
/>
)}
</Box>
{appState.startChat && (
<Box flexDirection="column">
{appState.isCancelling ? (
<CancellingIndicator />
) : appState.isThinking ? (
<ThinkingIndicator
contextSize={appState.thinkingStats.contextSize}
totalTokensUsed={appState.thinkingStats.totalTokensUsed}
tokensPerSecond={appState.thinkingStats.tokensPerSecond}
/>
) : null}
{appState.isModelSelectionMode ? (
<ModelSelector
client={appState.client}
currentModel={appState.currentModel}
onModelSelect={modeHandlers.handleModelSelect}
onCancel={modeHandlers.handleModelSelectionCancel}
/>
) : appState.isProviderSelectionMode ? (
<ProviderSelector
currentProvider={appState.currentProvider}
onProviderSelect={modeHandlers.handleProviderSelect}
onCancel={modeHandlers.handleProviderSelectionCancel}
<UIStateProvider>
<Box flexDirection="column" padding={1} width="100%">
<Box flexGrow={1} flexDirection="column" minHeight={0}>
<WelcomeMessage />
{appState.startChat && (
<ChatQueue
staticComponents={staticComponents}
queuedComponents={appState.chatComponents}
/>
) : appState.isThemeSelectionMode ? (
<ThemeSelector
onThemeSelect={modeHandlers.handleThemeSelect}
onCancel={modeHandlers.handleThemeSelectionCancel}
/>
) : appState.isToolConfirmationMode &&
appState.pendingToolCalls[appState.currentToolIndex] ? (
<ToolConfirmation
toolCall={appState.pendingToolCalls[appState.currentToolIndex]}
onConfirm={toolHandler.handleToolConfirmation}
onCancel={toolHandler.handleToolConfirmationCancel}
/>
) : appState.isToolExecuting &&
appState.pendingToolCalls[appState.currentToolIndex] ? (
<ToolExecutionIndicator
toolName={
appState.pendingToolCalls[appState.currentToolIndex].function
.name
}
currentIndex={appState.currentToolIndex}
totalTools={appState.pendingToolCalls.length}
/>
) : appState.isBashExecuting ? (
<BashExecutionIndicator command={appState.currentBashCommand} />
) : appState.mcpInitialized && appState.client ? (
<UserInput
customCommands={Array.from(appState.customCommandCache.keys())}
onSubmit={handleMessageSubmit}
disabled={
appState.isThinking ||
appState.isToolExecuting ||
appState.isBashExecuting
}
onCancel={handleCancel}
/>
) : appState.mcpInitialized && !appState.client ? (
<Text color={themeContextValue.colors.secondary}>
⚠️ No LLM provider available. Chat is disabled. Please fix your
provider configuration and restart.
</Text>
) : (
<Text color={themeContextValue.colors.secondary}>
<Spinner type="dots2" /> Loading...
</Text>
)}
</Box>
)}
</Box>
{appState.startChat && (
<Box flexDirection="column">
{appState.isCancelling ? (
<CancellingIndicator />
) : appState.isThinking ? (
<ThinkingIndicator
contextSize={appState.thinkingStats.contextSize}
totalTokensUsed={appState.thinkingStats.totalTokensUsed}
tokensPerSecond={appState.thinkingStats.tokensPerSecond}
/>
) : null}
{appState.isModelSelectionMode ? (
<ModelSelector
client={appState.client}
currentModel={appState.currentModel}
onModelSelect={modeHandlers.handleModelSelect}
onCancel={modeHandlers.handleModelSelectionCancel}
/>
) : appState.isProviderSelectionMode ? (
<ProviderSelector
currentProvider={appState.currentProvider}
onProviderSelect={modeHandlers.handleProviderSelect}
onCancel={modeHandlers.handleProviderSelectionCancel}
/>
) : appState.isThemeSelectionMode ? (
<ThemeSelector
onThemeSelect={modeHandlers.handleThemeSelect}
onCancel={modeHandlers.handleThemeSelectionCancel}
/>
) : appState.isToolConfirmationMode &&
appState.pendingToolCalls[appState.currentToolIndex] ? (
<ToolConfirmation
toolCall={
appState.pendingToolCalls[appState.currentToolIndex]
}
onConfirm={toolHandler.handleToolConfirmation}
onCancel={toolHandler.handleToolConfirmationCancel}
/>
) : appState.isToolExecuting &&
appState.pendingToolCalls[appState.currentToolIndex] ? (
<ToolExecutionIndicator
toolName={
appState.pendingToolCalls[appState.currentToolIndex]
.function.name
}
currentIndex={appState.currentToolIndex}
totalTools={appState.pendingToolCalls.length}
/>
) : appState.isBashExecuting ? (
<BashExecutionIndicator command={appState.currentBashCommand} />
) : appState.mcpInitialized && appState.client ? (
<UserInput
customCommands={Array.from(
appState.customCommandCache.keys(),
)}
onSubmit={handleMessageSubmit}
disabled={
appState.isThinking ||
appState.isToolExecuting ||
appState.isBashExecuting
}
onCancel={handleCancel}
/>
) : appState.mcpInitialized && !appState.client ? (
<Text color={themeContextValue.colors.secondary}>
⚠️ No LLM provider available. Chat is disabled. Please fix
your provider configuration and restart.
</Text>
) : (
<Text color={themeContextValue.colors.secondary}>
<Spinner type="dots2" /> Loading...
</Text>
)}
</Box>
)}
</Box>
</UIStateProvider>
</ThemeContext.Provider>
);
}
Loading