giga-code
Version:
A personal AI CLI assistant powered by Grok for local development.
164 lines • 9.77 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");
function ChatHistory({ entries }) {
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));
});
};
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, { 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"),
entry.metrics && !entry.isStreaming && (react_1.default.createElement(ink_1.Box, { marginTop: 1 },
react_1.default.createElement(ink_1.Text, { color: "blue" },
"prefill - ",
entry.metrics.prefillTimeMs,
"ms"),
react_1.default.createElement(ink_1.Text, { color: "white" }, " | "),
react_1.default.createElement(ink_1.Text, { color: "yellow" },
"decode - ",
entry.metrics.tokensPerSecond,
" toks/sec (",
entry.metrics.outputTokens,
" out / ",
entry.metrics.decodeTimeMs,
"ms)")))))));
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 "Bash";
case "create_todo_list":
return "Created Todo";
case "update_todo_list":
return "Updated Todo";
case "call_mcp_tool":
return "MCP";
default:
// Handle dynamic MCP tools (mcp_{serverName}_{toolName})
if (toolName.startsWith("mcp_")) {
const parts = toolName.split("_");
if (parts.length >= 3) {
const serverName = parts[1];
const actualToolName = parts.slice(2).join("_");
return `MCP:${serverName}`;
}
}
return "Tool";
}
};
const getToolFilePath = (toolCall) => {
if (toolCall?.function?.arguments) {
try {
const args = JSON.parse(toolCall.function.arguments);
// Handle todo tools and search tools specially - they don't have file paths
if (toolCall.function.name === "create_todo_list" ||
toolCall.function.name === "update_todo_list") {
return "";
}
// Handle MCP tools
if (toolCall.function.name === "call_mcp_tool") {
return args.tool_name || "unknown_tool";
}
// Handle dynamic MCP tools (mcp_{serverName}_{toolName})
if (toolCall.function.name.startsWith("mcp_")) {
const parts = toolCall.function.name.split("_");
if (parts.length >= 3) {
const actualToolName = parts.slice(2).join("_");
// Try to get a meaningful parameter for display
const displayParam = args.libraryName || args.library || args.query || args.path || args.url || actualToolName;
return displayParam;
}
}
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);
const shouldShowDiff = toolName === "str_replace_editor" || toolName === "create_file";
const shouldShowFileContent = toolName === "view_file";
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" }, 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 ",
truncateContent(entry.content)))),
shouldShowDiff && (react_1.default.createElement(ink_1.Box, { marginLeft: 4, flexDirection: "column" }, renderDiff(entry.content, filePath)))));
default:
return null;
}
};
const truncateContent = (content, maxLines = 5) => {
const lines = content.split('\n');
if (lines.length > maxLines) {
return lines.slice(0, maxLines).join('\n') + `\n... (truncated ${lines.length - maxLines} more lines)`;
}
return content;
};
return (react_1.default.createElement(ink_1.Box, { flexDirection: "column" }, entries.map(renderChatEntry)));
}
exports.ChatHistory = ChatHistory;
//# sourceMappingURL=chat-history.js.map