UNPKG

@vibe-kit/grok-cli

Version:

An open-source AI agent that brings the power of Grok directly into your terminal.

343 lines 16.9 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 DiffRenderer_1 = require("./components/DiffRenderer"); const cfonts_1 = __importDefault(require("cfonts")); function ChatInterface({ agent }) { const [input, setInput] = (0, react_1.useState)(""); const [chatHistory, setChatHistory] = (0, react_1.useState)([]); const [isProcessing, setIsProcessing] = (0, react_1.useState)(false); const [spinnerFrame, setSpinnerFrame] = (0, react_1.useState)(0); const [processingTime, setProcessingTime] = (0, react_1.useState)(0); const [tokenCount, setTokenCount] = (0, react_1.useState)(0); const [loadingTextIndex, setLoadingTextIndex] = (0, react_1.useState)(0); const [isStreaming, setIsStreaming] = (0, react_1.useState)(false); const { exit } = (0, ink_1.useApp)(); const scrollRef = (0, react_1.useRef)(); const processingStartTime = (0, react_1.useRef)(0); const loadingTexts = [ "Thinking...", "Computing...", "Analyzing...", "Processing...", "Calculating...", "Interfacing...", "Optimizing...", "Synthesizing...", "Decrypting...", "Calibrating...", "Bootstrapping...", "Synchronizing...", "Compiling...", "Downloading...", ]; (0, react_1.useEffect)(() => { // Display ASCII art banner console.clear(); cfonts_1.default.say("GROK", { font: "3d", align: "left", colors: ["magenta", "gray"], space: true, maxLength: "0", gradient: ["magenta", "cyan"], independentGradient: false, transitionGradient: true, env: "node", }); console.log("Tips for getting started:"); console.log("1. Ask questions, edit files, or run commands."); console.log("2. Be specific for the best results."); console.log("3. Create GROK.md files to customize your interactions with Grok."); console.log("4. /help for more information."); console.log(""); setChatHistory([]); }, []); // Spinner animation effect (0, react_1.useEffect)(() => { if (!isProcessing && !isStreaming) return; const spinnerFrames = ["/", "-", "\\", "|"]; const interval = setInterval(() => { setSpinnerFrame((prev) => (prev + 1) % spinnerFrames.length); }, 250); return () => clearInterval(interval); }, [isProcessing, isStreaming]); // Loading text rotation effect (0, react_1.useEffect)(() => { if (!isProcessing && !isStreaming) { return; } // Set random initial text setLoadingTextIndex(Math.floor(Math.random() * loadingTexts.length)); const interval = setInterval(() => { setLoadingTextIndex(Math.floor(Math.random() * loadingTexts.length)); }, 2000); return () => clearInterval(interval); }, [isProcessing, isStreaming, loadingTexts.length]); // Timer effect (0, react_1.useEffect)(() => { if (!isProcessing && !isStreaming) { setProcessingTime(0); return; } if (processingStartTime.current === 0) { processingStartTime.current = Date.now(); } const interval = setInterval(() => { setProcessingTime(Math.floor((Date.now() - processingStartTime.current) / 1000)); }, 1000); return () => clearInterval(interval); }, [isProcessing, isStreaming]); (0, ink_1.useInput)(async (inputChar, key) => { if (key.ctrl && inputChar === "c") { exit(); return; } if (key.escape && (isProcessing || isStreaming)) { setIsProcessing(false); setIsStreaming(false); setTokenCount(0); setProcessingTime(0); processingStartTime.current = 0; return; } if (key.return) { const userInput = input.trim(); if (userInput === "exit" || userInput === "quit") { exit(); return; } if (userInput) { // Add user message immediately for immediate display const userEntry = { type: "user", content: userInput, timestamp: new Date(), }; setChatHistory((prev) => [...prev, userEntry]); 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) { // Update or create streaming entry if (!streamingEntry) { // Create new streaming entry const newStreamingEntry = { type: 'assistant', content: chunk.content, timestamp: new Date(), isStreaming: true }; setChatHistory(prev => [...prev, newStreamingEntry]); streamingEntry = newStreamingEntry; } else { // Update existing streaming entry setChatHistory(prev => prev.map((entry, idx) => idx === prev.length - 1 && entry.isStreaming ? { ...entry, content: entry.content + chunk.content } : entry)); } } break; case 'tool_calls': // Don't handle tool calls in streaming - they're already in the assistant message break; case 'tool_result': if (chunk.toolCall && chunk.toolResult) { // Mark any streaming entries as complete 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': 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; } return; } if (key.backspace || key.delete) { setInput((prev) => prev.slice(0, -1)); return; } if (inputChar && !key.ctrl && !key.meta) { setInput((prev) => prev + inputChar); } }); const renderDiff = (diffContent, filename) => { return (react_1.default.createElement(DiffRenderer_1.DiffRenderer, { diffContent: diffContent, filename: filename, terminalWidth: 80 })); }; const renderChatEntry = (entry, index) => { switch (entry.type) { case "user": return (react_1.default.createElement(ink_1.Box, { key: index, flexDirection: "column", marginTop: 1 }, react_1.default.createElement(ink_1.Box, null, react_1.default.createElement(ink_1.Text, { color: "gray" }, ">", " ", entry.content)))); case "assistant": return (react_1.default.createElement(ink_1.Box, { key: index, flexDirection: "column", marginTop: 1 }, react_1.default.createElement(ink_1.Box, null, react_1.default.createElement(ink_1.Text, { color: "white" }, "\u23FA ", entry.content, entry.isStreaming && react_1.default.createElement(ink_1.Text, { color: "cyan" }, "\u2588"))), entry.toolCalls && entry.toolCalls.map((toolCall, tcIndex) => (react_1.default.createElement(ink_1.Box, { key: tcIndex, marginLeft: 2 }, react_1.default.createElement(ink_1.Text, { color: "magenta" }, "\uD83D\uDD27 ", toolCall.function.name)))))); case "tool_result": const getToolActionName = (toolName) => { switch (toolName) { case "view_file": return "Read"; case "str_replace_editor": return "Update"; case "create_file": return "Create"; case "bash": return "Execute"; default: return "Tool"; } }; const getToolFilePath = (toolCall) => { if (toolCall?.function?.arguments) { try { const args = JSON.parse(toolCall.function.arguments); return args.path || args.file_path || args.command || "unknown"; } catch { return "unknown"; } } return "unknown"; }; const toolName = entry.toolCall?.function?.name || "unknown"; const actionName = getToolActionName(toolName); const filePath = getToolFilePath(entry.toolCall); // Show diff only for write/update tools const shouldShowDiff = toolName === "str_replace_editor" || toolName === "create_file"; const getToolSummary = () => { if (!entry.toolResult?.success) return "Failed"; switch (toolName) { case "view_file": const lineCount = entry.content.split("\n").length; return `Read ${lineCount} lines`; case "bash": return "Command executed"; case "str_replace_editor": // Extract summary from first line of diff const firstLine = entry.content.split('\n')[0]; return firstLine.startsWith('Updated ') ? firstLine.substring(8) : "Updated with changes"; case "create_file": // Extract summary from first line of output const createFirstLine = entry.content.split('\n')[0]; return createFirstLine.startsWith('Created ') ? createFirstLine.substring(8) : "File created"; default: return `${actionName} completed`; } }; return (react_1.default.createElement(ink_1.Box, { key: index, flexDirection: "column" }, react_1.default.createElement(ink_1.Box, null, react_1.default.createElement(ink_1.Text, { color: "magenta" }, "\u23FA"), react_1.default.createElement(ink_1.Text, { color: "white" }, " ", actionName, "(", filePath, ")")), react_1.default.createElement(ink_1.Box, { marginLeft: 2 }, react_1.default.createElement(ink_1.Text, { color: "gray" }, "\u23BF ", getToolSummary())), shouldShowDiff && (react_1.default.createElement(ink_1.Box, { marginLeft: 6, flexDirection: "column" }, renderDiff(entry.content, filePath))))); default: return null; } }; return (react_1.default.createElement(ink_1.Box, { flexDirection: "column", padding: 1 }, 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.")), react_1.default.createElement(ink_1.Box, { flexDirection: "column", ref: scrollRef }, chatHistory.slice(-20).map(renderChatEntry)), (isProcessing || isStreaming) && (react_1.default.createElement(ink_1.Box, { marginTop: 1 }, react_1.default.createElement(ink_1.Text, { color: "cyan" }, ["/", "-", "\\", "|"][spinnerFrame], " ", loadingTexts[loadingTextIndex], " "), react_1.default.createElement(ink_1.Text, { color: "gray" }, "(", processingTime, "s \u00B7 \u2191 ", tokenCount, " tokens \u00B7 esc to interrupt)"))), react_1.default.createElement(ink_1.Box, { borderStyle: "round", borderColor: "gray", paddingX: 1, marginTop: 1 }, react_1.default.createElement(ink_1.Text, { color: "gray" }, "\u276F "), react_1.default.createElement(ink_1.Text, null, input, !isProcessing && !isStreaming && react_1.default.createElement(ink_1.Text, { color: "white" }, "\u2588"))))); } exports.default = ChatInterface; //# sourceMappingURL=chat-interface.js.map