@vibe-kit/grok-cli
Version:
An open-source AI agent that brings the power of Grok directly into your terminal.
164 lines • 9.15 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ChatHistory = void 0;
const react_1 = __importDefault(require("react"));
const ink_1 = require("ink");
const diff_renderer_1 = require("./diff-renderer");
const markdown_renderer_1 = require("../utils/markdown-renderer");
// Memoized ChatEntry component to prevent unnecessary re-renders
const MemoizedChatEntry = react_1.default.memo(({ entry, index }) => {
const renderDiff = (diffContent, filename) => {
return (react_1.default.createElement(diff_renderer_1.DiffRenderer, { diffContent: diffContent, filename: filename, terminalWidth: 80 }));
};
const renderFileContent = (content) => {
const lines = content.split("\n");
// Calculate minimum indentation like DiffRenderer does
let baseIndentation = Infinity;
for (const line of lines) {
if (line.trim() === "")
continue;
const firstCharIndex = line.search(/\S/);
const currentIndent = firstCharIndex === -1 ? 0 : firstCharIndex;
baseIndentation = Math.min(baseIndentation, currentIndent);
}
if (!isFinite(baseIndentation)) {
baseIndentation = 0;
}
return lines.map((line, index) => {
const displayContent = line.substring(baseIndentation);
return (react_1.default.createElement(ink_1.Text, { key: index, color: "gray" }, displayContent));
});
};
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, { flexDirection: "row", alignItems: "flex-start" },
react_1.default.createElement(ink_1.Text, { color: "white" }, "\u23FA "),
react_1.default.createElement(ink_1.Box, { flexDirection: "column", flexGrow: 1 },
entry.toolCalls ? (
// If there are tool calls, just show plain text
react_1.default.createElement(ink_1.Text, { color: "white" }, entry.content.trim())) : (
// If no tool calls, render as markdown
react_1.default.createElement(markdown_renderer_1.MarkdownRenderer, { content: entry.content.trim() })),
entry.isStreaming && react_1.default.createElement(ink_1.Text, { color: "cyan" }, "\u2588")))));
case "tool_call":
case "tool_result":
const getToolActionName = (toolName) => {
// Handle MCP tools with mcp__servername__toolname format
if (toolName.startsWith("mcp__")) {
const parts = toolName.split("__");
if (parts.length >= 3) {
const serverName = parts[1];
const actualToolName = parts.slice(2).join("__");
return `${serverName.charAt(0).toUpperCase() + serverName.slice(1)}(${actualToolName.replace(/_/g, " ")})`;
}
}
switch (toolName) {
case "view_file":
return "Read";
case "str_replace_editor":
return "Update";
case "create_file":
return "Create";
case "bash":
return "Bash";
case "search":
return "Search";
case "create_todo_list":
return "Created Todo";
case "update_todo_list":
return "Updated Todo";
default:
return "Tool";
}
};
const toolName = entry.toolCall?.function?.name || "unknown";
const actionName = getToolActionName(toolName);
const getFilePath = (toolCall) => {
if (toolCall?.function?.arguments) {
try {
const args = JSON.parse(toolCall.function.arguments);
if (toolCall.function.name === "search") {
return args.query;
}
return args.path || args.file_path || args.command || "";
}
catch {
return "";
}
}
return "";
};
const filePath = getFilePath(entry.toolCall);
const isExecuting = entry.type === "tool_call" || !entry.toolResult;
// Format JSON content for better readability
const formatToolContent = (content, toolName) => {
if (toolName.startsWith("mcp__")) {
try {
// Try to parse as JSON and format it
const parsed = JSON.parse(content);
if (Array.isArray(parsed)) {
// For arrays, show a summary instead of full JSON
return `Found ${parsed.length} items`;
}
else if (typeof parsed === 'object') {
// For objects, show a formatted version
return JSON.stringify(parsed, null, 2);
}
}
catch {
// If not JSON, return as is
return content;
}
}
return content;
};
const shouldShowDiff = entry.toolCall?.function?.name === "str_replace_editor" &&
entry.toolResult?.success &&
entry.content.includes("Updated") &&
entry.content.includes("---") &&
entry.content.includes("+++");
const shouldShowFileContent = (entry.toolCall?.function?.name === "view_file" ||
entry.toolCall?.function?.name === "create_file") &&
entry.toolResult?.success &&
!shouldShowDiff;
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: "magenta" }, "\u23FA"),
react_1.default.createElement(ink_1.Text, { color: "white" },
" ",
filePath ? `${actionName}(${filePath})` : actionName)),
react_1.default.createElement(ink_1.Box, { marginLeft: 2, flexDirection: "column" }, isExecuting ? (react_1.default.createElement(ink_1.Text, { color: "cyan" }, "\u23BF Executing...")) : shouldShowFileContent ? (react_1.default.createElement(ink_1.Box, { flexDirection: "column" },
react_1.default.createElement(ink_1.Text, { color: "gray" }, "\u23BF File contents:"),
react_1.default.createElement(ink_1.Box, { marginLeft: 2, flexDirection: "column" }, renderFileContent(entry.content)))) : shouldShowDiff ? (
// For diff results, show only the summary line, not the raw content
react_1.default.createElement(ink_1.Text, { color: "gray" },
"\u23BF ",
entry.content.split("\n")[0])) : (react_1.default.createElement(ink_1.Text, { color: "gray" },
"\u23BF ",
formatToolContent(entry.content, toolName)))),
shouldShowDiff && !isExecuting && (react_1.default.createElement(ink_1.Box, { marginLeft: 4, flexDirection: "column" }, renderDiff(entry.content, filePath)))));
default:
return null;
}
});
MemoizedChatEntry.displayName = "MemoizedChatEntry";
function ChatHistory({ entries, isConfirmationActive = false, }) {
// Filter out tool_call entries with "Executing..." when confirmation is active
const filteredEntries = isConfirmationActive
? entries.filter((entry) => !(entry.type === "tool_call" && entry.content === "Executing..."))
: entries;
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column" }, filteredEntries.slice(-20).map((entry, index) => (react_1.default.createElement(MemoizedChatEntry, { key: `${entry.timestamp.getTime()}-${index}`, entry: entry, index: index })))));
}
exports.ChatHistory = ChatHistory;
//# sourceMappingURL=chat-history.js.map