这是indexloc提供的服务,不要输入任何密码
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
132 changes: 71 additions & 61 deletions src/ui/components/chat-history.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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}>
Expand Down Expand Up @@ -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(
Expand All @@ -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>
);
}
11 changes: 10 additions & 1 deletion src/ui/components/chat-interface.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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(" ");
Expand Down
17 changes: 12 additions & 5 deletions src/ui/components/loading-spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,22 @@ 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);

useEffect(() => {
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]);
Expand All @@ -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]);
Expand All @@ -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 changes: 6 additions & 0 deletions test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
def dummy_function():
"""A dummy function for testing purposes."""
return "Hello from dummy function!"

if __name__ == "__main__":
print(dummy_function())
Loading