diff --git a/package.json b/package.json index c4bb5b1b..11208546 100644 --- a/package.json +++ b/package.json @@ -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": { diff --git a/src/ui/components/chat-history.tsx b/src/ui/components/chat-history.tsx index c1011c99..076be161 100644 --- a/src/ui/components/chat-history.tsx +++ b/src/ui/components/chat-history.tsx @@ -9,46 +9,44 @@ interface ChatHistoryProps { isConfirmationActive?: boolean; } -export function ChatHistory({ - entries, - isConfirmationActive = false, -}: ChatHistoryProps) { - const renderDiff = (diffContent: string, filename?: string) => { - return ( - - ); - }; - - 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 ( - - {displayContent} - + ); - }); - }; + }; + + 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 ( + + {displayContent} + + ); + }); + }; - 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 ( @@ -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 ( - {filteredEntries.slice(-20).map(renderChatEntry)} + {filteredEntries.slice(-20).map((entry, index) => ( + + ))} ); } diff --git a/src/ui/components/chat-interface.tsx b/src/ui/components/chat-interface.tsx index 52ee3c56..527bb6e5 100644 --- a/src/ui/components/chat-interface.tsx +++ b/src/ui/components/chat-interface.tsx @@ -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(" "); diff --git a/src/ui/components/loading-spinner.tsx b/src/ui/components/loading-spinner.tsx index 8baa8049..9b341519 100644 --- a/src/ui/components/loading-spinner.tsx +++ b/src/ui/components/loading-spinner.tsx @@ -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]}{" "} - ({processingTime}s · ↑ {formatTokenCount(tokenCount)} tokens · esc to interrupt) + ({processingTime}s · ↑ {formatTokenCount(tokenCount)} tokens · esc to + interrupt) ); -} \ No newline at end of file +} diff --git a/test.py b/test.py new file mode 100644 index 00000000..c18c0369 --- /dev/null +++ b/test.py @@ -0,0 +1,6 @@ +def dummy_function(): + """A dummy function for testing purposes.""" + return "Hello from dummy function!" + +if __name__ == "__main__": + print(dummy_function()) \ No newline at end of file