fix flickering UI in Powershell (#36)

* fix rendering issues in Ink

* fix tool names
This commit is contained in:
Ismail Pelaseyed authored and GitHub committed 2025-07-23 22:57:16 +02:00
1 parent 5095eb636d
commit e04a6ee126
5 files changed
+100 -68

No files matched your search

+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@vibe-kit/grok-cli",
"version": "0.0.8",
"version": "0.0.9",
"description": "An open-source AI agent that brings the power of Grok directly into your terminal.",
"main": "dist/index.js",
"bin": {
+71 -61
View File
@@ -9,46 +9,44 @@ interface ChatHistoryProps {
isConfirmationActive?: boolean;
}
export function ChatHistory({
entries,
isConfirmationActive = false,
}: ChatHistoryProps) {
const renderDiff = (diffContent: string, filename?: string) => {
return (
<DiffRenderer
diffContent={diffContent}
filename={filename}
terminalWidth={80}
/>
);
};
const renderFileContent = (content: string) => {
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);
// Memoized ChatEntry component to prevent unnecessary re-renders
const MemoizedChatEntry = React.memo(
({ entry, index }: { entry: ChatEntry; index: number }) => {
const renderDiff = (diffContent: string, filename?: string) => {
return (
<Text key={index} color="gray">
{displayContent}
</Text>
<DiffRenderer
diffContent={diffContent}
filename={filename}
terminalWidth={80}
/>
);
});
};
};
const renderFileContent = (content: string) => {
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 (
<Text key={index} color="gray">
{displayContent}
</Text>
);
});
};
const renderChatEntry = (entry: ChatEntry, index: number) => {
switch (entry.type) {
case "user":
return (
@@ -103,39 +101,38 @@ export function ChatHistory({
}
};
const getToolFilePath = (toolCall: any) => {
const toolName = entry.toolCall?.function?.name || "unknown";
const actionName = getToolActionName(toolName);
const getFilePath = (toolCall: any) => {
if (toolCall?.function?.arguments) {
try {
const args = JSON.parse(toolCall.function.arguments);
// Handle todo tools specially - they don't have file paths
if (
toolCall.function.name === "create_todo_list" ||
toolCall.function.name === "update_todo_list"
) {
return "";
}
// Handle search tool specially - show the query
if (toolCall.function.name === "search") {
return args.query || "unknown";
return args.query;
}
return args.path || args.file_path || args.command || "unknown";
return args.path || args.file_path || args.command || "";
} catch {
return "unknown";
return "";
}
}
return "unknown";
return "";
};
const toolName = entry.toolCall?.function?.name || "unknown";
const actionName = getToolActionName(toolName);
const filePath = getToolFilePath(entry.toolCall);
const filePath = getFilePath(entry.toolCall);
const isExecuting = entry.type === "tool_call" || !entry.toolResult;
const shouldShowDiff =
toolName === "str_replace_editor" || toolName === "create_file";
const shouldShowFileContent = toolName === "view_file";
const isExecuting = entry.type === "tool_call";
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 (
<Box key={index} flexDirection="column" marginTop={1}>
@@ -174,8 +171,15 @@ export function ChatHistory({
default:
return null;
}
};
}
);
MemoizedChatEntry.displayName = "MemoizedChatEntry";
export function ChatHistory({
entries,
isConfirmationActive = false,
}: ChatHistoryProps) {
// Filter out tool_call entries with "Executing..." when confirmation is active
const filteredEntries = isConfirmationActive
? entries.filter(
@@ -186,7 +190,13 @@ export function ChatHistory({
return (
<Box flexDirection="column">
{filteredEntries.slice(-20).map(renderChatEntry)}
{filteredEntries.slice(-20).map((entry, index) => (
<MemoizedChatEntry
key={`${entry.timestamp.getTime()}-${index}`}
entry={entry}
index={index}
/>
))}
</Box>
);
}
+10 -1
View File
@@ -57,7 +57,16 @@ function ChatInterfaceWithAgent({ agent }: { agent: GrokAgent }) {
});
useEffect(() => {
console.clear();
// Only clear console on non-Windows platforms or if not PowerShell
// Windows PowerShell can have issues with console.clear() causing flickering
const isWindows = process.platform === "win32";
const isPowerShell =
process.env.ComSpec?.toLowerCase().includes("powershell") ||
process.env.PSModulePath !== undefined;
if (!isWindows || !isPowerShell) {
console.clear();
}
// Add top padding
console.log(" ");
+12 -5
View File
@@ -25,7 +25,11 @@ const loadingTexts = [
"Downloading...",
];
export function LoadingSpinner({ isActive, processingTime, tokenCount }: LoadingSpinnerProps) {
export function LoadingSpinner({
isActive,
processingTime,
tokenCount,
}: LoadingSpinnerProps) {
const [spinnerFrame, setSpinnerFrame] = useState(0);
const [loadingTextIndex, setLoadingTextIndex] = useState(0);
@@ -33,9 +37,10 @@ export function LoadingSpinner({ isActive, processingTime, tokenCount }: Loading
if (!isActive) return;
const spinnerFrames = ["/", "-", "\\", "|"];
// Reduced frequency: 500ms instead of 250ms to reduce flickering on Windows
const interval = setInterval(() => {
setSpinnerFrame((prev) => (prev + 1) % spinnerFrames.length);
}, 250);
}, 500);
return () => clearInterval(interval);
}, [isActive]);
@@ -45,9 +50,10 @@ export function LoadingSpinner({ isActive, processingTime, tokenCount }: Loading
setLoadingTextIndex(Math.floor(Math.random() * loadingTexts.length));
// Increased interval: 4s instead of 2s to reduce state changes
const interval = setInterval(() => {
setLoadingTextIndex(Math.floor(Math.random() * loadingTexts.length));
}, 2000);
}, 4000);
return () => clearInterval(interval);
}, [isActive]);
@@ -62,8 +68,9 @@ export function LoadingSpinner({ isActive, processingTime, tokenCount }: Loading
{spinnerFrames[spinnerFrame]} {loadingTexts[loadingTextIndex]}{" "}
</Text>
<Text color="gray">
({processingTime}s · ↑ {formatTokenCount(tokenCount)} tokens · esc to interrupt)
({processingTime}s · ↑ {formatTokenCount(tokenCount)} tokens · esc to
interrupt)
</Text>
</Box>
);
}
}
+6
View File
@@ -0,0 +1,6 @@
def dummy_function():
"""A dummy function for testing purposes."""
return "Hello from dummy function!"
if __name__ == "__main__":
print(dummy_function())