giga-code
Version:
A personal AI CLI assistant powered by Grok for local development.
426 lines • 25.7 kB
JavaScript
"use strict";
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;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const react_1 = __importStar(require("react"));
const ink_1 = require("ink");
const use_input_handler_1 = require("../../hooks/use-input-handler");
const loading_spinner_1 = require("./loading-spinner");
const command_suggestions_1 = require("./command-suggestions");
const token_progress_bar_1 = __importDefault(require("./token-progress-bar"));
const model_selection_1 = require("./model-selection");
const route_selection_1 = require("./route-selection");
const temperature_selector_1 = require("./temperature-selector");
const expert_models_1 = require("./expert-models");
const provider_settings_1 = __importDefault(require("./provider-settings"));
const add_model_1 = __importDefault(require("./add-model"));
const delete_model_1 = __importDefault(require("./delete-model"));
const add_prompt_1 = __importDefault(require("./add-prompt"));
const delete_prompt_1 = __importDefault(require("./delete-prompt"));
const prompts_list_1 = __importDefault(require("./prompts-list"));
const add_mcp_server_1 = __importDefault(require("./add-mcp-server"));
const delete_mcp_server_1 = __importDefault(require("./delete-mcp-server"));
const mcp_server_selection_1 = require("./mcp-server-selection");
const chat_history_1 = require("./chat-history");
const conversation_history_1 = require("./conversation-history");
const chat_input_1 = require("./chat-input");
const file_finder_1 = require("./file-finder");
const confirmation_dialog_1 = __importDefault(require("./confirmation-dialog"));
const confirmation_service_1 = require("../../utils/confirmation-service");
const api_key_input_1 = __importDefault(require("./api-key-input"));
const exa_api_key_input_1 = __importDefault(require("./exa-api-key-input"));
const added_models_1 = require("../../utils/added-models");
const prompts_1 = require("../../utils/prompts");
const added_mcp_servers_1 = require("../../utils/added-mcp-servers");
const conversation_manager_1 = require("../../utils/conversation-manager");
const session_manager_1 = require("../../utils/session-manager");
const mode_manager_1 = require("../../utils/mode-manager");
const types_1 = require("../../types");
const added_mcp_servers_2 = require("../../utils/added-mcp-servers");
const global_settings_1 = require("../../utils/global-settings");
const cfonts_1 = __importDefault(require("cfonts"));
// Memoized ChatHistory component to prevent unnecessary re-renders
const ChatHistoryMemo = react_1.default.memo(chat_history_1.ChatHistory);
// Component showing session info and mode status
const SessionStatus = react_1.default.memo(function SessionStatus({ currentModel, currentMode }) {
const sessionInfo = session_manager_1.sessionManager.getSessionInfo();
const displayModel = currentModel || sessionInfo?.currentModel || '';
const noModelConfigured = !displayModel || displayModel.trim() === '';
// Memoize expensive operations
const mcpServers = react_1.default.useMemo(() => (0, added_mcp_servers_2.loadAddedMcpServers)(), []);
const enabledMcpCount = react_1.default.useMemo(() => mcpServers.filter(server => server.enabled).length, [mcpServers]);
// Get RAG status
const ragEnabled = react_1.default.useMemo(() => {
const globalSettings = global_settings_1.GlobalSettingsManager.getInstance();
return globalSettings.isRagEnabled();
}, []);
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
react_1.default.createElement(ink_1.Box, { flexDirection: "row", marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { bold: true, color: "green" }, "Session: "),
react_1.default.createElement(ink_1.Text, { color: "gray" }, (sessionInfo?.instanceId?.slice(0, 8)) || 'unknown'),
react_1.default.createElement(ink_1.Text, { color: "gray" }, " | Model: "),
noModelConfigured ? (react_1.default.createElement(ink_1.Text, { bold: true, color: "red" }, "Not configured")) : (react_1.default.createElement(ink_1.Text, { bold: true, color: "cyan" }, displayModel || 'Unknown')),
react_1.default.createElement(ink_1.Text, { color: "gray" }, " | MCPs: "),
react_1.default.createElement(ink_1.Text, { color: enabledMcpCount > 0 ? "green" : "red" }, enabledMcpCount),
react_1.default.createElement(ink_1.Text, { color: "gray" }, " | RAG: "),
react_1.default.createElement(ink_1.Text, { color: ragEnabled ? "green" : "red" }, ragEnabled ? "ON" : "OFF")),
react_1.default.createElement(ink_1.Box, { marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { bold: true, color: currentMode === types_1.AgentMode.GIGA ? 'yellow' : currentMode === types_1.AgentMode.CHILL ? 'green' : 'blue' }, mode_manager_1.modeManager.getModeDisplayName()),
react_1.default.createElement(ink_1.Text, { dimColor: true },
" - ",
mode_manager_1.modeManager.getModeDescription())),
noModelConfigured && (react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { bold: true, color: "yellow" }, "\u26A0\uFE0F First-time setup required:"),
react_1.default.createElement(ink_1.Text, { color: "gray" }, "1. Configure API keys: /providers"),
react_1.default.createElement(ink_1.Text, { color: "gray" }, "2. Add models: /add-model"),
react_1.default.createElement(ink_1.Text, { color: "gray" }, "3. Select a model: /models")))));
});
// Main chat component that handles input when agent is available
function ChatInterfaceWithAgent({ agent }) {
const [chatHistory, setChatHistory] = (0, react_1.useState)([]);
const [isProcessing, setIsProcessing] = (0, react_1.useState)(false);
const [processingTime, setProcessingTime] = (0, react_1.useState)(0);
const [tokenCount, setTokenCount] = (0, react_1.useState)(0);
const [tokenTrackerInfo, setTokenTrackerInfo] = (0, react_1.useState)(agent.getTokenTrackerInfo());
const [isStreaming, setIsStreaming] = (0, react_1.useState)(false);
const [statusMessage, setStatusMessage] = (0, react_1.useState)(null);
const [confirmationOptions, setConfirmationOptions] = (0, react_1.useState)(null);
const [currentConversationId, setCurrentConversationId] = (0, react_1.useState)(null);
const [currentMode, setCurrentMode] = (0, react_1.useState)(mode_manager_1.modeManager.getCurrentMode());
const scrollRef = (0, react_1.useRef)();
const processingStartTime = (0, react_1.useRef)(0);
const hasInitialized = (0, react_1.useRef)(false);
const confirmationService = confirmation_service_1.ConfirmationService.getInstance();
const conversationManager = conversation_manager_1.ConversationManager.getInstance();
const { input, showCommandSuggestions, selectedCommandIndex, showModelSelection, selectedModelIndex, showProviderSettings, selectedProviderIndex, showAddModel, showDeleteModel, showPromptsList, showAddPrompt, showDeletePrompt, showMcpServers, showAddMcpServer, showDeleteMcpServer, selectedMcpServerIndex, showConversationHistory, showTemperatureSelector, currentTemperature, showExpertModels, showRouteSelection, routeViewMode, selectedRouteModelIndex, selectedRouteProviderIndex, currentSelectedModel, routeProviders, isLoadingProviders, showFileFinder, selectedFileIndex, filteredFiles, fileQuery, commandSuggestions, availableModels, mcpServers, providerList, closeProviderSettings, closeAddModel, closeDeleteModel, closePromptsList, closeAddPrompt, closeDeletePrompt, closeMcpServers, closeAddMcpServer, closeDeleteMcpServer, closeConversationHistory, closeTemperatureSelector, closeExpertModels, closeRouteSelection, closeFileFinder, refreshModels, refreshMcpServers, openRouterModels, showExaApiKeyInput, closeExaApiKeyInput, } = (0, use_input_handler_1.useInputHandler)({
agent,
chatHistory,
setChatHistory,
setIsProcessing,
setIsStreaming,
setTokenCount,
setProcessingTime,
setStatusMessage,
processingStartTime,
isProcessing,
isStreaming,
isConfirmationActive: !!confirmationOptions,
onModeChange: setCurrentMode,
});
(0, react_1.useEffect)(() => {
// Only show banner on initial mount, not on re-renders
if (!hasInitialized.current) {
cfonts_1.default.say("GIGA", {
font: "3d",
align: "left",
colors: ["magenta", "gray"],
space: true,
maxLength: "0",
gradient: ["magenta", "cyan"],
independentGradient: false,
transitionGradient: true,
env: "node",
});
hasInitialized.current = true;
setChatHistory([]);
}
}, []); // Remove dependencies to run only once on mount
(0, react_1.useEffect)(() => {
const handleConfirmationRequest = (options) => {
setConfirmationOptions(options);
};
confirmationService.on('confirmation-requested', handleConfirmationRequest);
const handleStatusUpdate = (message) => {
setStatusMessage(message);
};
agent.on('status', handleStatusUpdate);
return () => {
confirmationService.off('confirmation-requested', handleConfirmationRequest);
agent.off('status', handleStatusUpdate);
};
}, [confirmationService, agent]);
(0, react_1.useEffect)(() => {
if (!isProcessing && !isStreaming) {
setProcessingTime(0);
processingStartTime.current = 0;
return;
}
if (processingStartTime.current === 0) {
processingStartTime.current = Date.now();
}
const interval = setInterval(() => {
// Double-check we're still processing before updating
if (processingStartTime.current > 0) {
setProcessingTime(Math.floor((Date.now() - processingStartTime.current) / 1000));
}
}, 1000);
return () => clearInterval(interval);
}, [isProcessing, isStreaming]);
const handleConfirmation = (dontAskAgain) => {
confirmationService.confirmOperation(true, dontAskAgain);
setConfirmationOptions(null);
};
const handleRejection = (feedback) => {
confirmationService.rejectOperation(feedback);
setConfirmationOptions(null);
// Reset processing states when operation is cancelled
setIsProcessing(false);
setIsStreaming(false);
setTokenCount(0);
setProcessingTime(0);
processingStartTime.current = 0;
setTokenTrackerInfo(agent.getTokenTrackerInfo());
};
const handleAddModel = (providerName, modelName) => {
// Save the model to storage
(0, added_models_1.addModel)(modelName, providerName);
const confirmEntry = {
type: "assistant",
content: `✓ Added model: ${modelName} from ${providerName}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
refreshModels(); // Refresh the available models list
closeAddModel();
};
const handleDeleteModel = (modelName, providerName) => {
// Delete the model from storage
const success = (0, added_models_1.deleteModel)(modelName, providerName);
if (success) {
const confirmEntry = {
type: "assistant",
content: `✓ Deleted model: ${modelName} from ${providerName}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
refreshModels(); // Refresh the available models list
}
else {
const errorEntry = {
type: "assistant",
content: `❌ Failed to delete model: ${modelName}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
closeDeleteModel();
};
const handleAddPrompt = (name, content) => {
// Add the prompt
(0, prompts_1.addPrompt)(name, content);
const confirmEntry = {
type: "assistant",
content: `✓ Added custom prompt: ${name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
closeAddPrompt();
};
const handleDeletePrompt = (name) => {
// Delete the prompt
const success = (0, prompts_1.deletePrompt)(name);
if (success) {
const confirmEntry = {
type: "assistant",
content: `✓ Deleted custom prompt: ${name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
}
else {
const errorEntry = {
type: "assistant",
content: `❌ Failed to delete prompt: ${name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
closeDeletePrompt();
};
const handleAddMcpServer = (name, command, args, env, description) => {
// Save the MCP server to storage
(0, added_mcp_servers_1.addMcpServer)(name, command, args, env, description);
const confirmEntry = {
type: "assistant",
content: `✓ Added MCP server: ${name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
refreshMcpServers(); // Refresh the available servers list
// Refresh MCP connections in the agent
agent.refreshMcpConnections();
closeAddMcpServer();
};
const handleDeleteMcpServer = (name) => {
// Delete the MCP server from storage
const success = (0, added_mcp_servers_1.deleteMcpServer)(name);
if (success) {
const confirmEntry = {
type: "assistant",
content: `✓ Deleted MCP server: ${name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
refreshMcpServers(); // Refresh the available servers list
// Refresh MCP connections in the agent
agent.refreshMcpConnections();
}
else {
const errorEntry = {
type: "assistant",
content: `❌ Failed to delete MCP server: ${name}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
closeDeleteMcpServer();
};
const handleSelectConversation = async (conversationId) => {
try {
// Save current conversation if there are messages
if (chatHistory.length > 0 && currentConversationId) {
await conversationManager.saveConversation(chatHistory, agent.getCurrentModel(), currentConversationId);
}
// Load the selected conversation
const conversation = await conversationManager.loadConversation(conversationId);
if (conversation) {
// Update the model if it's different
if (conversation.model !== agent.getCurrentModel()) {
agent.setModel(conversation.model);
}
// Restore conversation state in the agent (this restores AI context)
agent.restoreConversation(conversation.messages);
// Update UI state
setChatHistory(conversation.messages);
setCurrentConversationId(conversationId);
const switchEntry = {
type: "assistant",
content: `📚 Switched to conversation: **${conversation.title}**`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, switchEntry]);
}
}
catch (error) {
console.error('Failed to switch conversation:', error);
const errorEntry = {
type: "assistant",
content: `❌ Failed to load conversation: ${error instanceof Error ? error.message : 'Unknown error'}`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, errorEntry]);
}
};
// Auto-save current conversation when chat history changes
(0, react_1.useEffect)(() => {
if (chatHistory.length > 0) {
const saveConversation = async () => {
try {
const conversationId = await conversationManager.saveConversation(chatHistory, agent.getCurrentModel(), currentConversationId || undefined);
if (!currentConversationId) {
setCurrentConversationId(conversationId);
}
}
catch (error) {
console.error('Failed to auto-save conversation:', error);
}
};
// Debounce the save operation
const timeoutId = setTimeout(saveConversation, 1000);
return () => clearTimeout(timeoutId);
}
}, [chatHistory, agent, conversationManager, currentConversationId]);
const handleExaApiKeySet = () => {
const confirmEntry = {
type: "assistant",
content: `✓ EXA API key saved successfully.`,
timestamp: new Date(),
};
setChatHistory((prev) => [...prev, confirmEntry]);
closeExaApiKeyInput();
};
(0, react_1.useEffect)(() => {
const interval = setInterval(() => {
setTokenTrackerInfo(agent.getTokenTrackerInfo());
}, 100);
return () => clearInterval(interval);
}, [agent]);
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 },
react_1.default.createElement(SessionStatus, { currentModel: agent.getCurrentModel(), currentMode: currentMode }),
react_1.default.createElement(ink_1.Box, { flexDirection: "column", marginBottom: 1 },
react_1.default.createElement(ink_1.Text, { dimColor: true }, "Type your request in natural language. Type 'exit' or Ctrl+C to quit. Shift+Tab to cycle modes.")),
react_1.default.createElement(ink_1.Box, { key: "chat-main", flexDirection: "column", ref: scrollRef },
react_1.default.createElement(ChatHistoryMemo, { entries: chatHistory })),
tokenTrackerInfo && (react_1.default.createElement(token_progress_bar_1.default, { current: tokenTrackerInfo.current, max: tokenTrackerInfo.max, model: tokenTrackerInfo.model })),
!confirmationOptions && (react_1.default.createElement(react_1.default.Fragment, null,
react_1.default.createElement(loading_spinner_1.LoadingSpinner, { isActive: isProcessing || isStreaming, processingTime: processingTime, tokenCount: tokenCount, statusMessage: statusMessage }),
!showAddPrompt && !showDeletePrompt && !showPromptsList && (react_1.default.createElement(chat_input_1.ChatInput, { input: input, isProcessing: isProcessing, isStreaming: isStreaming, currentModel: agent.getCurrentModel() })),
!showAddPrompt && !showDeletePrompt && !showPromptsList && (react_1.default.createElement(command_suggestions_1.CommandSuggestions, { suggestions: commandSuggestions, input: input, selectedIndex: selectedCommandIndex, isVisible: showCommandSuggestions })),
!showAddPrompt && !showDeletePrompt && !showPromptsList && (react_1.default.createElement(file_finder_1.FileFinder, { files: filteredFiles, selectedIndex: selectedFileIndex, query: fileQuery, isVisible: showFileFinder })),
!showAddPrompt && !showDeletePrompt && !showPromptsList && (react_1.default.createElement(model_selection_1.ModelSelection, { models: availableModels, selectedIndex: selectedModelIndex, isVisible: showModelSelection, currentModel: agent.getCurrentModel() })),
!showAddPrompt && !showDeletePrompt && !showPromptsList && (react_1.default.createElement(route_selection_1.RouteSelection, { models: openRouterModels, selectedModelIndex: selectedRouteModelIndex, selectedProviderIndex: selectedRouteProviderIndex, isVisible: showRouteSelection, currentModel: agent.getCurrentModel(), viewMode: routeViewMode, currentSelectedModel: currentSelectedModel, providers: routeProviders, isLoadingProviders: isLoadingProviders, onModelSelect: () => { }, onProviderSelect: () => { }, onBack: () => { } })),
react_1.default.createElement(temperature_selector_1.TemperatureSelector, { temperature: currentTemperature, isVisible: showTemperatureSelector }),
showExpertModels && (react_1.default.createElement(expert_models_1.ExpertModels, { onExit: closeExpertModels })),
showProviderSettings && (react_1.default.createElement(provider_settings_1.default, { providers: providerList, selectedIndex: selectedProviderIndex, onClose: closeProviderSettings })),
showAddModel && (react_1.default.createElement(add_model_1.default, { providers: providerList, onClose: closeAddModel, onAddModel: handleAddModel })),
showDeleteModel && (react_1.default.createElement(delete_model_1.default, { onClose: closeDeleteModel, onDeleteModel: handleDeleteModel })),
showPromptsList && (react_1.default.createElement(prompts_list_1.default, { onClose: closePromptsList, onSelectPrompt: (promptName) => {
if (agent) {
agent.setSelectedCustomPrompt(promptName);
// Add a system message to chat history showing the change
const promptDisplayName = promptName || "GIGA (Default)";
const systemMessage = {
type: "assistant",
content: `🎯 System prompt changed to: **${promptDisplayName}**`,
timestamp: new Date(),
};
// Add to chat history
setChatHistory(prev => [...prev, systemMessage]);
}
}, selectedPrompt: agent?.getSelectedCustomPrompt() || null })),
showAddPrompt && (react_1.default.createElement(add_prompt_1.default, { onClose: closeAddPrompt, onAddPrompt: handleAddPrompt })),
showDeletePrompt && (react_1.default.createElement(delete_prompt_1.default, { onClose: closeDeletePrompt, onDeletePrompt: handleDeletePrompt })),
showConversationHistory && (react_1.default.createElement(conversation_history_1.ConversationHistory, { isVisible: showConversationHistory, onClose: closeConversationHistory, onSelectConversation: handleSelectConversation })),
react_1.default.createElement(mcp_server_selection_1.McpServerSelection, { servers: mcpServers, selectedIndex: selectedMcpServerIndex, isVisible: showMcpServers }),
showAddMcpServer && (react_1.default.createElement(add_mcp_server_1.default, { onClose: closeAddMcpServer, onAddServer: handleAddMcpServer })),
showDeleteMcpServer && (react_1.default.createElement(delete_mcp_server_1.default, { onClose: closeDeleteMcpServer, onDeleteServer: handleDeleteMcpServer })),
showExaApiKeyInput && (react_1.default.createElement(exa_api_key_input_1.default, { onClose: closeExaApiKeyInput, onApiKeySet: handleExaApiKeySet })))),
confirmationOptions && (react_1.default.createElement(confirmation_dialog_1.default, { operation: confirmationOptions.operation, filename: confirmationOptions.filename, showVSCodeOpen: confirmationOptions.showVSCodeOpen, content: confirmationOptions.content, onConfirm: handleConfirmation, onReject: handleRejection }))));
}
// Main component that handles API key input or chat interface
function ChatInterface({ agent }) {
const [currentAgent, setCurrentAgent] = (0, react_1.useState)(agent || null);
const handleApiKeySet = (newAgent) => {
setCurrentAgent(newAgent);
};
if (!currentAgent) {
return react_1.default.createElement(api_key_input_1.default, { onApiKeySet: handleApiKeySet });
}
return react_1.default.createElement(ChatInterfaceWithAgent, { agent: currentAgent });
}
exports.default = ChatInterface;
//# sourceMappingURL=chat-interface.js.map