giga-code
Version:
A personal AI CLI assistant powered by Grok for local development.
1,140 lines (1,135 loc) • 48.4 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.useInputHandler = void 0;
const react_1 = require("react");
const ink_1 = require("ink");
const confirmation_service_1 = require("../utils/confirmation-service");
const added_mcp_servers_1 = require("../utils/added-mcp-servers");
const fuzzy_search_1 = require("../utils/fuzzy-search");
const instance_models_1 = require("../utils/instance-models");
const file_finder_1 = require("../utils/file-finder");
const added_models_1 = require("../utils/added-models");
const session_manager_1 = require("../utils/session-manager");
const mode_manager_1 = require("../utils/mode-manager");
// Helper function to get OpenRouter models consistently
const getOpenRouterModels = (models) => {
return models.filter(model => model.description.includes('(OpenRouter)'));
};
function useInputHandler({ agent, chatHistory, setChatHistory, setIsProcessing, setIsStreaming, setTokenCount, setProcessingTime, setStatusMessage, processingStartTime, isProcessing, isStreaming, isConfirmationActive = false, onModeChange, }) {
const [input, setInput] = (0, react_1.useState)("");
const [showCommandSuggestions, setShowCommandSuggestions] = (0, react_1.useState)(false);
const [selectedCommandIndex, setSelectedCommandIndex] = (0, react_1.useState)(0);
const [showModelSelection, setShowModelSelection] = (0, react_1.useState)(false);
const [selectedModelIndex, setSelectedModelIndex] = (0, react_1.useState)(0);
const [showProviderSettings, setShowProviderSettings] = (0, react_1.useState)(false);
const [selectedProviderIndex, setSelectedProviderIndex] = (0, react_1.useState)(0);
const [showAddModel, setShowAddModel] = (0, react_1.useState)(false);
const [showDeleteModel, setShowDeleteModel] = (0, react_1.useState)(false);
const [showPromptsList, setShowPromptsList] = (0, react_1.useState)(false);
const [showAddPrompt, setShowAddPrompt] = (0, react_1.useState)(false);
const [showDeletePrompt, setShowDeletePrompt] = (0, react_1.useState)(false);
const [showMcpServers, setShowMcpServers] = (0, react_1.useState)(false);
const [showAddMcpServer, setShowAddMcpServer] = (0, react_1.useState)(false);
const [showDeleteMcpServer, setShowDeleteMcpServer] = (0, react_1.useState)(false);
const [selectedMcpServerIndex, setSelectedMcpServerIndex] = (0, react_1.useState)(0);
const [dynamicModels, setDynamicModels] = (0, react_1.useState)([]);
const [allModels, setAllModels] = (0, react_1.useState)([]);
const [mcpServers, setMcpServers] = (0, react_1.useState)([]);
const [showConversationHistory, setShowConversationHistory] = (0, react_1.useState)(false);
const [showTemperatureSelector, setShowTemperatureSelector] = (0, react_1.useState)(false);
const [currentTemperature, setCurrentTemperature] = (0, react_1.useState)(0.7);
const [showExpertModels, setShowExpertModels] = (0, react_1.useState)(false);
const [showExaApiKeyInput, setShowExaApiKeyInput] = (0, react_1.useState)(false);
// Route selection state
const [showRouteSelection, setShowRouteSelection] = (0, react_1.useState)(false);
const [routeViewMode, setRouteViewMode] = (0, react_1.useState)('models');
const [selectedRouteModelIndex, setSelectedRouteModelIndex] = (0, react_1.useState)(0);
const [selectedRouteProviderIndex, setSelectedRouteProviderIndex] = (0, react_1.useState)(0);
const [currentSelectedModel, setCurrentSelectedModel] = (0, react_1.useState)('');
const [routeProviders, setRouteProviders] = (0, react_1.useState)([]);
const [isLoadingProviders, setIsLoadingProviders] = (0, react_1.useState)(false);
// Command history state
const [commandHistory, setCommandHistory] = (0, react_1.useState)([]);
const [historyIndex, setHistoryIndex] = (0, react_1.useState)(-1);
const [temporaryInput, setTemporaryInput] = (0, react_1.useState)("");
// File finder state
const [showFileFinder, setShowFileFinder] = (0, react_1.useState)(false);
const [selectedFileIndex, setSelectedFileIndex] = (0, react_1.useState)(0);
const [availableFiles, setAvailableFiles] = (0, react_1.useState)([]);
const [filteredFiles, setFilteredFiles] = (0, react_1.useState)([]);
const [fileQuery, setFileQuery] = (0, react_1.useState)("");
// Streaming content batching state
const streamingBufferRef = (0, react_1.useRef)("");
const batchTimeoutRef = (0, react_1.useRef)(null);
// Double Ctrl+C detection as fallback
const lastCtrlCRef = (0, react_1.useRef)(0);
const ctrlCTimeoutRef = (0, react_1.useRef)(null);
// Batched state update for streaming content
const flushStreamingBuffer = (0, react_1.useCallback)(() => {
if (streamingBufferRef.current) {
const content = streamingBufferRef.current;
streamingBufferRef.current = "";
setChatHistory((prev) => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming
? { ...entry, content: entry.content + content }
: entry));
}
if (batchTimeoutRef.current) {
clearTimeout(batchTimeoutRef.current);
batchTimeoutRef.current = null;
}
}, [setChatHistory]);
// Debounced streaming content update
const addStreamingContent = (0, react_1.useCallback)((content) => {
streamingBufferRef.current += content;
if (batchTimeoutRef.current) {
clearTimeout(batchTimeoutRef.current);
}
batchTimeoutRef.current = setTimeout(() => {
setImmediate(flushStreamingBuffer);
}, 16); // 16ms debounce for ~60fps updates
}, [flushStreamingBuffer]);
// Helper function to add command to history
const addToHistory = (command) => {
if (command.trim() && command !== commandHistory[commandHistory.length - 1]) {
setCommandHistory(prev => [...prev, command.trim()]);
}
setHistoryIndex(-1);
setTemporaryInput("");
};
const closeProviderSettings = () => {
setShowProviderSettings(false);
setSelectedProviderIndex(0);
};
const closeAddModel = () => {
setShowAddModel(false);
};
const closeDeleteModel = () => {
setShowDeleteModel(false);
};
const closePromptsList = () => {
setShowPromptsList(false);
};
const closeAddPrompt = () => {
setShowAddPrompt(false);
};
const closeDeletePrompt = () => {
setShowDeletePrompt(false);
};
const closeMcpServers = () => {
setShowMcpServers(false);
setSelectedMcpServerIndex(0);
};
const closeAddMcpServer = () => {
setShowAddMcpServer(false);
};
const closeDeleteMcpServer = () => {
setShowDeleteMcpServer(false);
};
const closeRouteSelection = () => {
setShowRouteSelection(false);
setRouteViewMode('models');
setSelectedRouteModelIndex(0);
setSelectedRouteProviderIndex(0);
setCurrentSelectedModel('');
setRouteProviders([]);
setIsLoadingProviders(false);
};
const closeConversationHistory = () => {
setShowConversationHistory(false);
};
const closeTemperatureSelector = () => {
setShowTemperatureSelector(false);
};
const closeExpertModels = () => {
setShowExpertModels(false);
};
const closeExaApiKeyInput = () => {
setShowExaApiKeyInput(false);
};
const closeFileFinder = () => {
setShowFileFinder(false);
setSelectedFileIndex(0);
setFilteredFiles([]);
setFileQuery("");
};
const updateFileFinder = (currentInput) => {
const queryInfo = (0, file_finder_1.extractFileQuery)(currentInput);
if (queryInfo) {
const { query, isDirectory } = queryInfo;
setFileQuery(query);
if (isDirectory) {
// Handle directory search - use simple filtering
const filtered = (0, file_finder_1.getFilteredItems)(availableFiles, query, true);
setFilteredFiles(filtered);
setShowFileFinder(true); // Always show when @ query is active
setSelectedFileIndex(0);
}
else {
// Handle file search
if (query === '') {
// Just @ typed, show recent/common files
const recentFiles = availableFiles
.filter(file => !file.isDirectory)
.map(file => file.relativePath)
.sort()
.slice(0, 10);
setFilteredFiles(recentFiles);
}
else {
// Use fuzzy search for better matching
const filtered = (0, fuzzy_search_1.fuzzySearch)(query, availableFiles.filter(file => !file.isDirectory), (file) => file.relativePath, 10).map(file => file.relativePath);
setFilteredFiles(filtered);
}
setShowFileFinder(true); // Always show when @ query is active
setSelectedFileIndex(0);
}
}
else {
setShowFileFinder(false);
setFilteredFiles([]);
setFileQuery("");
}
};
const refreshModels = (allModelsData) => {
if (allModelsData) {
setAllModels(allModelsData);
}
const instanceModels = (0, instance_models_1.getInstanceAvailableModels)();
const modelOptions = instanceModels.map(model => ({
model: model.model,
description: `${model.description}${model.isFavorite ? ' ⭐' : ''}${model.isRecentlyUsed ? ' 🕒' : ''}`
}));
setDynamicModels(modelOptions);
};
const refreshMcpServers = () => {
const addedServers = (0, added_mcp_servers_1.loadAddedMcpServers)();
setMcpServers(addedServers);
};
// Helper function to get current filtered suggestions based on input
const getFilteredSuggestions = (currentInput) => {
return currentInput.startsWith("/")
? (0, fuzzy_search_1.fuzzySearch)(currentInput.substring(1), // Remove the "/" for fuzzy matching
commandSuggestions.filter(s => s.command.startsWith("/")), (suggestion) => suggestion.command.substring(1), // Remove "/" for matching
8)
: (0, fuzzy_search_1.fuzzySearch)(currentInput, commandSuggestions, (suggestion) => suggestion.command, 8);
};
(0, react_1.useEffect)(() => {
refreshModels();
refreshMcpServers();
setCurrentTemperature(session_manager_1.sessionManager.getTemperature());
// Load available files
try {
const files = (0, file_finder_1.getAllFiles)();
setAvailableFiles(files);
}
catch (error) {
console.error('Failed to load files:', error);
}
// Cleanup function to clear any pending timeouts
return () => {
if (batchTimeoutRef.current) {
clearTimeout(batchTimeoutRef.current);
batchTimeoutRef.current = null;
}
if (ctrlCTimeoutRef.current) {
clearTimeout(ctrlCTimeoutRef.current);
ctrlCTimeoutRef.current = null;
}
};
}, []);
const { exit } = (0, ink_1.useApp)();
const commandSuggestions = [
{ command: "/help", description: "Show help information" },
{ command: "/intro", description: "Show getting started information" },
{ command: "/clear", description: "Start new conversation" },
{ command: "/history", description: "Browse conversation history" },
{ command: "/models", description: "Switch Grok Model" },
{ command: "/route", description: "Configure model provider routing" },
{ command: "/add-model", description: "Add models from providers" },
{ command: "/delete-model", description: "Delete added models" },
{ command: "/prompts", description: "View custom prompts" },
{ command: "/add-prompt", description: "Add custom prompt" },
{ command: "/delete-prompt", description: "Delete custom prompt" },
{ command: "/mcps", description: "View MCP servers" },
{ command: "/add-mcp", description: "Add MCP server" },
{ command: "/delete-mcp", description: "Delete MCP server" },
{ command: "/sampling", description: "Adjust sampling temperature" },
{ command: "/experts", description: "Configure expert model routing" },
{ command: "/providers", description: "Configure API Keys" },
{ command: "/exa", description: "Set your EXA API key for search" },
{ command: "/exit", description: "Exit the application" },
];
const availableModels = [];
const providerList = [
{ name: "OpenRouter", keyName: "openRouterApiKey", description: "OpenRouter API (Multi-model access)" },
{ name: "Anthropic", keyName: "anthropicApiKey", description: "Claude models" },
{ name: "Google", keyName: "googleApiKey", description: "Gemini models" },
{ name: "xAI", keyName: "xaiApiKey", description: "Grok models" },
{ name: "Groq", keyName: "groqApiKey", description: "Fast inference" },
{ name: "Cerebras", keyName: "cerebrasApiKey", description: "Cerebras models" },
{ name: "OpenAI", keyName: "openaiApiKey", description: "GPT models" },
{ name: "Ollama", keyName: "ollamaBaseUrl", description: "Local Ollama models" },
];
const handleDirectCommand = async (input) => {
const trimmedInput = input.trim();
if (trimmedInput === "/clear") {
// Clear console and show GIGA art again
console.clear();
const cfonts = require('cfonts');
cfonts.say("GIGA", {
font: "3d",
align: "left",
colors: ["magenta", "gray"],
space: true,
maxLength: "0",
gradient: ["magenta", "cyan"],
independentGradient: false,
transitionGradient: true,
env: "node",
});
// Reset chat history
setChatHistory([]);
// Reset processing states
setIsProcessing(false);
setIsStreaming(false);
setTokenCount(0);
setProcessingTime(0);
processingStartTime.current = 0;
// Reset confirmation service session flags
const confirmationService = confirmation_service_1.ConfirmationService.getInstance();
confirmationService.resetSession();
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/intro") {
const introEntry = {
type: "assistant",
content: `Getting started:
1. First time? Configure API keys: /providers
2. To enable web search, set your EXA API key: /exa
3. Add models from your providers: /add-model
4. Select your preferred model: /models
5. Ask questions, edit files, or run commands!
Need help? Type /help for more information.`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, introEntry]);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/help") {
const helpEntry = {
type: "assistant",
content: `GIGA Help:
Built-in Commands:
/clear - Start new conversation
/help - Show this help
/intro - Show getting started information
/history - Browse conversation history (Ctrl+H)
/models - Switch models
/route - Configure model provider routing
/add-model - Add models from providers
/delete-model - Delete added models
/prompts - View custom prompts
/add-prompt - Add custom prompt
/delete-prompt- Delete custom prompt
/mcps - View MCP servers
/add-mcp - Add MCP server
/delete-mcp - Delete MCP server
/sampling - Adjust sampling temperature
/experts - Configure expert model routing
/providers - Configure API keys
/exa - Set your EXA API key for search
/exit - Exit application
exit, quit - Exit application
Direct Commands (executed immediately):
ls [path] - List directory contents
pwd - Show current directory
cd <path> - Change directory
cat <file> - View file contents
mkdir <dir> - Create directory
touch <file>- Create empty file
For complex operations, just describe what you want in natural language.
Examples:
"edit package.json and add a new script"
"create a new React component called Header"
"show me all TypeScript files in this project"`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, helpEntry]);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/history") {
setShowConversationHistory(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/models") {
setShowModelSelection(true);
setSelectedModelIndex(0);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/providers") {
setShowProviderSettings(true);
setSelectedProviderIndex(0);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/add-model") {
setShowAddModel(true);
addToHistory(trimmedInput);
setInput("");
// Fetch models when the add model dialog is opened
const { fetchModelsWithFallback } = await Promise.resolve().then(() => __importStar(require('../utils/dynamic-model-fetcher')));
const { loadApiKeys } = await Promise.resolve().then(() => __importStar(require('../utils/api-keys')));
const keys = loadApiKeys();
const result = await fetchModelsWithFallback('openrouter', keys.openRouterApiKey || '');
if (result.allModels) {
refreshModels(result.allModels);
}
return true;
}
if (trimmedInput === "/delete-model") {
setShowDeleteModel(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/mcps") {
setShowMcpServers(true);
setSelectedMcpServerIndex(0);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/add-mcp") {
setShowAddMcpServer(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/delete-mcp") {
setShowDeleteMcpServer(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/prompts") {
setShowPromptsList(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/add-prompt") {
setShowAddPrompt(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/delete-prompt") {
setShowDeletePrompt(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/route") {
setShowRouteSelection(true);
setRouteViewMode('models');
setSelectedRouteModelIndex(0);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/sampling") {
setShowTemperatureSelector(true);
setCurrentTemperature(session_manager_1.sessionManager.getTemperature());
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/experts") {
setShowExpertModels(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput === "/exa") {
setShowExaApiKeyInput(true);
addToHistory(trimmedInput);
setInput("");
return true;
}
if (trimmedInput.startsWith("/models ")) {
const modelArg = trimmedInput.split(" ")[1];
const modelNames = dynamicModels.map(m => m.model);
if (modelNames.includes(modelArg)) {
(0, instance_models_1.onModelSelected)(modelArg);
agent.setModel(modelArg, allModels);
const confirmEntry = {
type: "assistant",
content: `✓ Switched to model: ${modelArg}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
}
else {
const errorEntry = {
type: "assistant",
content: `Invalid model: ${modelArg}
Available models: ${modelNames.join(", ")}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
addToHistory(trimmedInput);
setInput("");
return true;
}
const directBashCommands = [
"ls", "pwd", "cd", "cat", "mkdir", "touch", "echo", "grep", "find", "cp", "mv", "rm",
];
const firstWord = trimmedInput.split(" ")[0];
if (directBashCommands.includes(firstWord)) {
const userEntry = {
type: "user",
content: trimmedInput,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, userEntry]);
try {
const result = await agent.executeBashCommand(trimmedInput);
const commandEntry = {
type: "tool_result",
content: result.success
? result.output || "Command completed"
: result.error || "Command failed",
timestamp: new Date(),
toolCall: {
id: `bash_${Date.now()}`,
type: "function",
function: {
name: "bash",
arguments: JSON.stringify({ command: trimmedInput }),
},
},
toolResult: result,
};
setChatHistory((prev) => [...prev, commandEntry]);
}
catch (error) {
const errorEntry = {
type: "assistant",
content: `Error executing command: ${error.message}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
addToHistory(trimmedInput);
setInput("");
return true;
}
return false;
};
const processUserMessage = async (userInput) => {
const userEntry = {
type: "user",
content: userInput,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, userEntry]);
addToHistory(userInput);
setIsProcessing(true);
setInput("");
try {
setIsStreaming(true);
let streamingEntry = null;
for await (const chunk of agent.processUserMessageStream(userInput)) {
switch (chunk.type) {
case "content":
if (chunk.content) {
if (!streamingEntry) {
const newStreamingEntry = {
type: "assistant",
content: chunk.content,
timestamp: new Date(),
isStreaming: true,
};
setChatHistory((prev) => [...prev, newStreamingEntry]);
streamingEntry = newStreamingEntry;
}
else {
// Use batched streaming content update
addStreamingContent(chunk.content);
}
}
break;
case "token_count":
if (chunk.tokenCount !== undefined) {
setTokenCount(chunk.tokenCount);
}
break;
case "tool_calls":
if (chunk.toolCalls) {
// Stop streaming for the current assistant message
setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false, toolCalls: chunk.toolCalls } : entry));
streamingEntry = null;
}
break;
case "tool_result":
if (chunk.toolCall && chunk.toolResult) {
setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false } : entry));
const toolResultEntry = {
type: "tool_result",
content: chunk.toolResult.success
? chunk.toolResult.output || "Success"
: chunk.toolResult.error || "Error occurred",
timestamp: new Date(),
toolCall: chunk.toolCall,
toolResult: chunk.toolResult,
};
setChatHistory((prev) => [...prev, toolResultEntry]);
streamingEntry = null;
}
break;
case "done":
// Flush any remaining buffered content
flushStreamingBuffer();
if (streamingEntry) {
setChatHistory((prev) => prev.map((entry) => entry.isStreaming ? { ...entry, isStreaming: false } : entry));
}
setIsStreaming(false);
break;
}
}
}
catch (error) {
const errorEntry = {
type: "assistant",
content: `Error: ${error.message}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
setIsStreaming(false);
}
setIsProcessing(false);
processingStartTime.current = 0;
};
const handleRouteSelectionInput = async (inputChar, key) => {
if (key.escape) {
if (routeViewMode === 'providers') {
// Go back to models view
setRouteViewMode('models');
setSelectedRouteProviderIndex(0);
setCurrentSelectedModel('');
setRouteProviders([]);
setIsLoadingProviders(false);
}
else {
// Close route selection entirely
closeRouteSelection();
}
return;
}
if (routeViewMode === 'models') {
// Filter to only show OpenRouter models (same as the component filter)
const openRouterModels = getOpenRouterModels(dynamicModels);
if (key.upArrow) {
setSelectedRouteModelIndex((prev) => prev === 0 ? openRouterModels.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedRouteModelIndex((prev) => (prev + 1) % openRouterModels.length);
return;
}
if (key.return || key.tab) {
const selectedModel = openRouterModels[selectedRouteModelIndex];
console.log(`DEBUG: Selected model index: ${selectedRouteModelIndex}, Model: ${selectedModel?.model}`);
if (selectedModel) {
// Move to provider selection
setCurrentSelectedModel(selectedModel.model);
setRouteViewMode('providers');
setSelectedRouteProviderIndex(0);
setIsLoadingProviders(true);
setRouteProviders([]);
try {
const { loadApiKeys } = await Promise.resolve().then(() => __importStar(require('../utils/api-keys')));
const { getModelProvidersWithFallback } = await Promise.resolve().then(() => __importStar(require('../utils/openrouter-providers')));
const apiKeys = loadApiKeys();
const openRouterKey = apiKeys.openRouterApiKey;
if (openRouterKey) {
const modelProviders = await getModelProvidersWithFallback(selectedModel.model, openRouterKey);
setRouteProviders(modelProviders);
}
else {
// Show error message
const errorEntry = {
type: "assistant",
content: "OpenRouter API key is required to fetch providers. Please configure it in /providers.",
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
closeRouteSelection();
}
}
catch (error) {
console.error('Error fetching providers:', error);
setRouteProviders([]);
}
finally {
setIsLoadingProviders(false);
}
}
return;
}
}
else if (routeViewMode === 'providers') {
if (key.upArrow) {
setSelectedRouteProviderIndex((prev) => prev === 0 ? routeProviders.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedRouteProviderIndex((prev) => (prev + 1) % routeProviders.length);
return;
}
if (key.return || key.tab) {
const selectedProvider = routeProviders[selectedRouteProviderIndex];
console.log(`DEBUG: Setting provider ${selectedProvider?.id} for model ${currentSelectedModel}`);
if (selectedProvider && currentSelectedModel) {
// Save OpenRouter provider preference to the global models file
const success = (0, added_models_1.setOpenRouterProvider)(currentSelectedModel, selectedProvider.id);
console.log(`DEBUG: setOpenRouterProvider result: ${success}`);
if (success) {
// Show confirmation message
const confirmEntry = {
type: "assistant",
content: `✓ Set OpenRouter provider for ${currentSelectedModel}: ${selectedProvider.name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
}
else {
const errorEntry = {
type: "assistant",
content: `❌ Failed to set provider for ${currentSelectedModel} - model not found in OpenRouter`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
closeRouteSelection();
}
return;
}
}
};
(0, ink_1.useInput)(async (inputChar, key) => {
// Don't handle input if confirmation dialog or prompt dialogs are active
if (isConfirmationActive || showAddPrompt || showDeletePrompt || showPromptsList || showRouteSelection || showConversationHistory || showTemperatureSelector || showExpertModels || showExaApiKeyInput) {
// Special handling for route selection
if (showRouteSelection) {
await handleRouteSelectionInput(inputChar, key);
}
// Special handling for temperature selector
if (showTemperatureSelector) {
if (key.escape) {
closeTemperatureSelector();
return;
}
if (key.leftArrow) {
const newTemp = Math.max(0.0, currentTemperature - 0.1);
setCurrentTemperature(Math.round(newTemp * 10) / 10);
return;
}
if (key.rightArrow) {
const newTemp = Math.min(1.0, currentTemperature + 0.1);
setCurrentTemperature(Math.round(newTemp * 10) / 10);
return;
}
if (key.return) {
session_manager_1.sessionManager.setTemperature(currentTemperature);
const confirmEntry = {
type: "assistant",
content: `✓ Temperature set to ${currentTemperature.toFixed(1)}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
closeTemperatureSelector();
return;
}
}
return;
}
// Ctrl+C is handled by the process-level handler for immediate exit
if (key.ctrl && inputChar === "c") {
// Let the process-level handler deal with it
return;
}
// Handle Shift+Tab for mode cycling
if (key.shift && key.tab) {
const newMode = mode_manager_1.modeManager.cycleMode();
agent.updateMode(newMode);
if (onModeChange) {
onModeChange(newMode);
}
// Mode change will be reflected in the SessionStatus component automatically
// No need to add chat history entries for mode switches
return;
}
if (key.ctrl && inputChar === "h") {
setShowConversationHistory(true);
return;
}
if (key.escape) {
if (showCommandSuggestions) {
setShowCommandSuggestions(false);
setSelectedCommandIndex(0);
return;
}
if (showModelSelection) {
setShowModelSelection(false);
setSelectedModelIndex(0);
return;
}
if (showProviderSettings) {
setShowProviderSettings(false);
setSelectedProviderIndex(0);
return;
}
if (showAddModel) {
setShowAddModel(false);
return;
}
if (showDeleteModel) {
setShowDeleteModel(false);
return;
}
if (showPromptsList) {
setShowPromptsList(false);
return;
}
if (showAddPrompt) {
setShowAddPrompt(false);
return;
}
if (showDeletePrompt) {
setShowDeletePrompt(false);
return;
}
if (showConversationHistory) {
setShowConversationHistory(false);
return;
}
if (showTemperatureSelector) {
setShowTemperatureSelector(false);
return;
}
if (showExpertModels) {
setShowExpertModels(false);
return;
}
if (showExaApiKeyInput) {
closeExaApiKeyInput();
return;
}
if (showMcpServers) {
setShowMcpServers(false);
setSelectedMcpServerIndex(0);
return;
}
if (showAddMcpServer) {
setShowAddMcpServer(false);
return;
}
if (showDeleteMcpServer) {
setShowDeleteMcpServer(false);
return;
}
if (isProcessing || isStreaming) {
agent.abortCurrentOperation();
setIsProcessing(false);
setIsStreaming(false);
setTokenCount(0);
setProcessingTime(0);
processingStartTime.current = 0;
return;
}
}
if (showCommandSuggestions) {
const filteredSuggestions = getFilteredSuggestions(input);
if (key.upArrow) {
setSelectedCommandIndex((prev) => prev === 0 ? filteredSuggestions.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedCommandIndex((prev) => (prev + 1) % filteredSuggestions.length);
return;
}
if (key.tab || key.return) {
const selectedCommand = filteredSuggestions[selectedCommandIndex];
if (selectedCommand) {
setInput(selectedCommand.command + " ");
setShowCommandSuggestions(false);
setSelectedCommandIndex(0);
}
return;
}
}
if (showModelSelection) {
if (key.upArrow) {
setSelectedModelIndex((prev) => prev === 0 ? dynamicModels.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedModelIndex((prev) => (prev + 1) % dynamicModels.length);
return;
}
if (key.tab || key.return) {
const selectedModel = dynamicModels[selectedModelIndex];
(0, instance_models_1.onModelSelected)(selectedModel.model);
agent.setModel(selectedModel.model, allModels);
const confirmEntry = {
type: "assistant",
content: `✓ Switched to model: ${selectedModel.model}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
setShowModelSelection(false);
setSelectedModelIndex(0);
return;
}
}
if (showMcpServers) {
if (key.upArrow) {
setSelectedMcpServerIndex((prev) => prev === 0 ? mcpServers.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedMcpServerIndex((prev) => (prev + 1) % mcpServers.length);
return;
}
if (key.tab || key.return) {
const selectedServer = mcpServers[selectedMcpServerIndex];
if (selectedServer) {
const serverInfo = {
type: "assistant",
content: `MCP Server: ${selectedServer.name}
Command: ${selectedServer.command}
${selectedServer.args ? `Args: ${selectedServer.args.join(' ')}` : ''}
${selectedServer.env ? `Environment: ${Object.entries(selectedServer.env).map(([k, v]) => `${k}=${v}`).join(' ')}` : ''}
${selectedServer.description ? `Description: ${selectedServer.description}` : ''}
Added: ${new Date(selectedServer.dateAdded).toLocaleDateString()}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, serverInfo]);
}
setShowMcpServers(false);
setSelectedMcpServerIndex(0);
return;
}
}
if (showAddModel || showDeleteModel || showAddMcpServer || showDeleteMcpServer) {
return;
}
if (showProviderSettings) {
return;
}
// Handle file finder navigation when active (takes precedence over history)
if (showFileFinder && filteredFiles.length > 0) {
if (key.upArrow) {
setSelectedFileIndex((prev) => prev === 0 ? filteredFiles.length - 1 : prev - 1);
return;
}
if (key.downArrow) {
setSelectedFileIndex((prev) => (prev + 1) % filteredFiles.length);
return;
}
if (key.return || key.tab) {
const selectedFile = filteredFiles[selectedFileIndex];
if (selectedFile) {
const newInput = (0, file_finder_1.replaceFileQuery)(input, selectedFile);
setInput(newInput);
closeFileFinder();
}
return;
}
}
// Handle command history navigation with up/down arrows
if (key.upArrow && !showCommandSuggestions && !showModelSelection && !showMcpServers) {
if (commandHistory.length > 0) {
if (historyIndex === -1) {
// Store current input before navigating history
setTemporaryInput(input);
setHistoryIndex(commandHistory.length - 1);
setInput(commandHistory[commandHistory.length - 1]);
}
else if (historyIndex > 0) {
setHistoryIndex(historyIndex - 1);
setInput(commandHistory[historyIndex - 1]);
}
}
return;
}
if (key.downArrow && !showCommandSuggestions && !showModelSelection && !showMcpServers) {
if (commandHistory.length > 0 && historyIndex !== -1) {
if (historyIndex < commandHistory.length - 1) {
setHistoryIndex(historyIndex + 1);
setInput(commandHistory[historyIndex + 1]);
}
else {
// Return to original input
setHistoryIndex(-1);
setInput(temporaryInput);
}
}
return;
}
if (key.return) {
const userInput = input.trim();
if (userInput === "exit" || userInput === "quit") {
exit();
return;
}
if (userInput) {
const directCommandResult = await handleDirectCommand(userInput);
if (!directCommandResult) {
await processUserMessage(userInput);
}
}
return;
}
if (key.backspace || key.delete) {
const newInput = input.slice(0, -1);
setInput(newInput);
// Reset history navigation when user edits input
if (historyIndex !== -1) {
setHistoryIndex(-1);
setTemporaryInput("");
}
// Update file finder
updateFileFinder(newInput);
if (!newInput.startsWith("/")) {
setShowCommandSuggestions(false);
setSelectedCommandIndex(0);
}
else if (showCommandSuggestions) {
// Reset selected index when input changes to avoid out-of-bounds
setSelectedCommandIndex(0);
}
return;
}
if (inputChar && !key.ctrl && !key.meta) {
const newInput = input + inputChar;
setInput(newInput);
// Reset history navigation when user types new input
if (historyIndex !== -1) {
setHistoryIndex(-1);
setTemporaryInput("");
}
// Update file finder based on @ symbol
updateFileFinder(newInput);
if (newInput === "/" ||
["ls", "pwd", "cd", "cat", "mkdir", "touch"].some((cmd) => cmd.startsWith(newInput))) {
setShowCommandSuggestions(true);
setSelectedCommandIndex(0);
}
else if (!newInput.startsWith("/") &&
!["ls", "pwd", "cd", "cat", "mkdir", "touch"].some((cmd) => cmd.startsWith(newInput))) {
setShowCommandSuggestions(false);
setSelectedCommandIndex(0);
}
else if (showCommandSuggestions) {
// Reset selected index when input changes to avoid out-of-bounds
setSelectedCommandIndex(0);
}
}
});
return {
input,
showCommandSuggestions,
selectedCommandIndex,
showModelSelection,
selectedModelIndex,
showProviderSettings,
selectedProviderIndex,
showAddModel,
showDeleteModel,
showPromptsList,
showAddPrompt,
showDeletePrompt,
showMcpServers,
showAddMcpServer,
showDeleteMcpServer,
selectedMcpServerIndex,
showConversationHistory,
showTemperatureSelector,
currentTemperature,
showExpertModels,
showExaApiKeyInput,
showRouteSelection,
routeViewMode,
selectedRouteModelIndex,
selectedRouteProviderIndex,
currentSelectedModel,
routeProviders,
isLoadingProviders,
showFileFinder,
selectedFileIndex,
filteredFiles,
fileQuery,
commandSuggestions,
availableModels: dynamicModels,
mcpServers,
providerList,
closeProviderSettings,
closeAddModel,
closeDeleteModel,
closePromptsList,
closeAddPrompt,
closeDeletePrompt,
closeMcpServers,
closeAddMcpServer,
closeDeleteMcpServer,
closeConversationHistory,
closeTemperatureSelector,
closeExpertModels,
closeExaApiKeyInput,
closeRouteSelection,
closeFileFinder,
refreshModels,
refreshMcpServers,
openRouterModels: getOpenRouterModels(dynamicModels),
agent,
};
}
exports.useInputHandler = useInputHandler;
//# sourceMappingURL=use-input-handler.js.map