UNPKG

giga-code

Version:

A personal AI CLI assistant powered by Grok for local development.

426 lines 25.7 kB
"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