fix flickering UI in Powershell (#36)
* fix rendering issues in Ink * fix tool names
This commit is contained in:
1 parent
5095eb636d
commit
e04a6ee126
5 files changed
+100
-68
No files matched your search
+1
-1
@@ -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": {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
@@ -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(" ");
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user