From 4a0a4c79c69f2d307e877ffa64be22a4d4040901 Mon Sep 17 00:00:00 2001 From: TKS <32640296+bigsk1@users.noreply.github.com> Date: Thu, 18 Jul 2024 04:40:18 -0700 Subject: [PATCH] add console features --- backend.py | 149 ++++++++++++++++------------ frontend/src/components/Console.tsx | 118 +++++++++++----------- projects/claude_plus_ascii.py | 15 --- projects/sample.py | 19 ---- shared_utils.py | 8 +- 5 files changed, 152 insertions(+), 157 deletions(-) delete mode 100644 projects/claude_plus_ascii.py delete mode 100644 projects/sample.py diff --git a/backend.py b/backend.py index 542ba5d..998d13a 100644 --- a/backend.py +++ b/backend.py @@ -358,55 +358,100 @@ async def chat(request: ChatRequest): logger.error(f"Error in chat endpoint: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) -def get_executable(): +def get_shell(): + return "cmd.exe" if platform.system() == "Windows" else "/bin/bash" + +def execute_shell_command(command, cwd): + shell = get_shell() if platform.system() == "Windows": - return shutil.which("cmd.exe") + result = subprocess.run([shell, "/c", command], capture_output=True, text=True, cwd=cwd) else: - return shutil.which("bash") + result = subprocess.run([shell, "-c", command], capture_output=True, text=True, cwd=cwd) + return result.stdout + result.stderr -@api_router.post("/execute") -async def execute_command(request: CommandRequest): - try: - # Set the working directory to the projects folder +def ensure_in_projects_dir(): + if not os.getcwd().startswith(PROJECTS_DIR): os.chdir(PROJECTS_DIR) - + +@api_router.post("/console/execute") +async def console_execute_command(request: CommandRequest): + try: + ensure_in_projects_dir() + cwd = os.getcwd() command_parts = request.command.split() - if command_parts[0] == 'python': - # If it's a Python command, use the Python interpreter - executable = shutil.which("python") - if not executable: - raise FileNotFoundError("Python executable not found") - result = subprocess.run( - [executable] + command_parts[1:], - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=False - ) + cmd = command_parts[0].lower() + + if cmd == "cd": + return handle_cd(command_parts[1] if len(command_parts) > 1 else "") + elif cmd == "ls" or (cmd == "dir" and platform.system() == "Windows"): + return handle_ls(cwd) + elif cmd == "pwd": + return handle_pwd(cwd) + elif cmd == "echo": + return handle_echo(command_parts[1:], cwd) + elif cmd == "cat" or cmd == "type": + return handle_cat(command_parts[1] if len(command_parts) > 1 else "", cwd) + elif cmd == "mkdir": + return handle_mkdir(command_parts[1] if len(command_parts) > 1 else "", cwd) + elif cmd == "touch" or cmd == "echo.": + return handle_touch(command_parts[1] if len(command_parts) > 1 else "", cwd) else: - # For other commands, use the system shell - executable = get_executable() - if not executable: - raise FileNotFoundError("Shell executable not found") - result = subprocess.run( - [executable, "/c" if os.name == "nt" else "-c", request.command], - shell=False, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - text=True, - check=False - ) - - output = result.stdout + result.stderr - return {"result": output, "cwd": os.getcwd()} - except FileNotFoundError as e: - logger.error(f"FileNotFoundError in execute_command: {str(e)}", exc_info=True) - raise HTTPException(status_code=404, detail=str(e)) + output = execute_shell_command(request.command, cwd) + return {"result": output, "cwd": os.path.relpath(cwd, PROJECTS_DIR)} except Exception as e: - logger.error(f"Error in execute_command: {str(e)}", exc_info=True) + logger.error(f"Error in console_execute_command: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) +def handle_cd(path): + try: + new_path = os.path.abspath(os.path.join(os.getcwd(), path)) + if os.path.exists(new_path) and os.path.isdir(new_path) and new_path.startswith(PROJECTS_DIR): + os.chdir(new_path) + current_dir = os.path.relpath(os.getcwd(), PROJECTS_DIR) + return {"result": f"Changed directory to: {current_dir}", "cwd": current_dir} + else: + return {"result": f"Directory not found or access denied: {path}", "cwd": os.path.relpath(os.getcwd(), PROJECTS_DIR)} + except Exception as e: + return {"result": f"Error changing directory: {str(e)}", "cwd": os.path.relpath(os.getcwd(), PROJECTS_DIR)} + +def handle_ls(cwd): + items = os.listdir(cwd) + return {"result": "\n".join(items), "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + +def handle_pwd(cwd): + return {"result": cwd, "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + +def handle_echo(args, cwd): + return {"result": " ".join(args), "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + +def handle_cat(filename, cwd): + try: + with open(os.path.join(cwd, filename), 'r') as file: + content = file.read() + return {"result": content, "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + except Exception as e: + return {"result": f"Error reading file: {str(e)}", "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + +def handle_mkdir(dirname, cwd): + try: + os.mkdir(os.path.join(cwd, dirname)) + return {"result": f"Directory created: {dirname}", "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + except Exception as e: + return {"result": f"Error creating directory: {str(e)}", "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + +def handle_touch(filename, cwd): + try: + with open(os.path.join(cwd, filename), 'a'): + os.utime(os.path.join(cwd, filename), None) + return {"result": f"File touched: {filename}", "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + except Exception as e: + return {"result": f"Error touching file: {str(e)}", "cwd": os.path.relpath(cwd, PROJECTS_DIR)} + +@api_router.get("/console/cwd") +async def console_get_current_working_directory(): + ensure_in_projects_dir() + return {"cwd": os.path.relpath(os.getcwd(), PROJECTS_DIR)} + @api_router.post("/run_python") async def run_python(request: CommandRequest): try: @@ -431,28 +476,6 @@ async def run_python(request: CommandRequest): logger.error(f"Error in run_python: {str(e)}", exc_info=True) raise HTTPException(status_code=500, detail=str(e)) -@api_router.get("/cwd") -async def get_current_working_directory(): - return {"cwd": os.path.relpath(os.getcwd(), PROJECTS_DIR)} - -@api_router.get("/ls") -async def list_directory(path: str = "."): - try: - full_path = os.path.join(PROJECTS_DIR, path) - contents = os.listdir(full_path) - return DirectoryContents(path=path, contents=contents) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@api_router.post("/cd") -async def change_directory(request: CommandRequest): - try: - new_dir = os.path.join(PROJECTS_DIR, request.command) - os.chdir(new_dir) - return {"cwd": os.path.relpath(os.getcwd(), PROJECTS_DIR)} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - @api_router.post("/pip_install") async def pip_install(request: CommandRequest): try: @@ -462,7 +485,7 @@ async def pip_install(request: CommandRequest): check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - executable=get_executable() + executable=get_shell() ) output = result.stdout.decode('utf-8') + result.stderr.decode('utf-8') return {"result": output} diff --git a/frontend/src/components/Console.tsx b/frontend/src/components/Console.tsx index 03e504f..8270d41 100644 --- a/frontend/src/components/Console.tsx +++ b/frontend/src/components/Console.tsx @@ -1,66 +1,77 @@ -import React, { useState, useRef } from 'react'; -import axios, { AxiosError } from 'axios'; -import { - Box, Input, VStack, Text, useColorMode, Button, -} from '@chakra-ui/react'; +import React, { useState, useEffect, useRef } from 'react'; +import axios from 'axios'; +import { Box, Input, VStack, Text, useColorMode } from '@chakra-ui/react'; const API_URL = 'http://127.0.0.1:8000'; -interface ErrorResponse { - detail: string; +interface TerminalLine { + content: string; + isCommand: boolean; } const Console: React.FC = () => { const [input, setInput] = useState(''); - const [output, setOutput] = useState([]); - const [cwd, setCwd] = useState(''); - const outputRef = useRef(null); + const [history, setHistory] = useState([]); + const [currentDirectory, setCurrentDirectory] = useState(''); const { colorMode } = useColorMode(); + const terminalRef = useRef(null); - const runCommand = async (command: string) => { + useEffect(() => { + fetchCurrentDirectory(); + }, []); + + useEffect(() => { + if (terminalRef.current) { + terminalRef.current.scrollTop = terminalRef.current.scrollHeight; + } + }, [history]); + + const fetchCurrentDirectory = async () => { try { - const trimmedInput = command.trim(); - setOutput(prev => [...prev, `$ ${trimmedInput}`]); - - if (trimmedInput === 'clear') { - setOutput([]); - } else { - const response = await axios.post(`${API_URL}/api/execute`, { command: trimmedInput }); - setOutput(prev => [...prev, response.data.result]); - if (response.data.cwd) { - setCwd(response.data.cwd); - } - } + const response = await axios.get(`${API_URL}/api/console/cwd`); + setCurrentDirectory(response.data.cwd); } catch (error) { - console.error('Error executing command:', error); - let errorMessage = 'Error executing command. Please check the console for details.'; + console.error('Error fetching current directory:', error); + addToHistory('Error fetching current directory', false); + } + }; - if (axios.isAxiosError(error)) { - const axiosError = error as AxiosError; - if (axiosError.response) { - errorMessage = `Error: ${axiosError.response.data.detail || 'Unknown error'}`; - } else if (axiosError.request) { - errorMessage = 'Error: No response received from server'; - } else { - errorMessage = `Error: ${axiosError.message}`; - } - } + const addToHistory = (content: string, isCommand: boolean) => { + setHistory(prev => [...prev, { content, isCommand }]); + }; - setOutput(prev => [...prev, errorMessage]); + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!input.trim()) return; + + addToHistory(`${currentDirectory}$ ${input}`, true); + + if (input.trim().toLowerCase() === 'clear') { + setHistory([]); + setInput(''); + return; } - setInput(''); - }; - const handleKeyDown = async (e: React.KeyboardEvent) => { - if (e.key === 'Enter') { - await runCommand(input); + try { + const response = await axios.post(`${API_URL}/api/console/execute`, { command: input }); + if (response.data.result) { + addToHistory(response.data.result, false); + } + if (response.data.cwd) { + setCurrentDirectory(response.data.cwd); + } + } catch (error) { + console.error('Error executing command:', error); + addToHistory('Error executing command. Please check the console for details.', false); } + + setInput(''); }; return ( { whiteSpace="pre-wrap" textAlign="left" > - {output.map((line, index) => ( - {line} + {history.map((line, index) => ( + + {line.content} + ))} - - {cwd}$ +
setInput(e.target.value)} - onKeyDown={handleKeyDown} - placeholder="Enter command (type 'clear' to clear console)" - flex={1} + placeholder={`${currentDirectory}$ `} + bg={colorMode === 'dark' ? 'gray.700' : 'white'} + color={colorMode === 'dark' ? 'white' : 'black'} /> - - - - - - - +
); }; diff --git a/projects/claude_plus_ascii.py b/projects/claude_plus_ascii.py deleted file mode 100644 index 3060596..0000000 --- a/projects/claude_plus_ascii.py +++ /dev/null @@ -1,15 +0,0 @@ -# Claude Plus ASCII Art - -def print_claude_plus(): - ascii_art = ''' - ____ _ _ ____ _ - / ___|| | __ _ _ _ __| | ___ | _ \| |_ _ ___ -| | | |/ _` | | | |/ _` |/ _ \ | |_) | | | | / __| -| | | | (_| | |_| | (_| | __/ | __/| | |_| \__ \\ - \____ |_|\__,_|\__,_|\__,_|\___| |_| |_|\__,_|___/ - - ''' - print(ascii_art) - -if __name__ == "__main__": - print_claude_plus() \ No newline at end of file diff --git a/projects/sample.py b/projects/sample.py deleted file mode 100644 index a0bf4f9..0000000 --- a/projects/sample.py +++ /dev/null @@ -1,19 +0,0 @@ -# This is a sample Python file - -def greet(name): - """ - A simple function to greet a person - """ - return f"Hello, {name}! Welcome to Python programming." - -def calculate_sum(a, b): - """ - A function to calculate the sum of two numbers - """ - return a + b - -if __name__ == "__main__": - # Example usage of the functions - print(greet("Alice")) - result = calculate_sum(5, 7) - print(f"The sum of 5 and 7 is: {result}") \ No newline at end of file diff --git a/shared_utils.py b/shared_utils.py index 32ee668..a51b2e7 100644 --- a/shared_utils.py +++ b/shared_utils.py @@ -69,15 +69,15 @@ 2. Provide the full path starting with 'projects/' 3. Include any initial content if specified -Example usage: +Example usages: create_file({{"path": "/hello.txt", "content": "Hello, world!"}}) -After using a tool, report the result to the user and ask if they want to take any further actions. +After using a tool and completing the task, report the result to the user and ask if they want to take any further actions. Important guidelines: 1. Always use the appropriate tool for file operations and searches. Don't just describe actions, perform them. -2. All file operations are restricted to the 'projects' directory for security reasons. You cannot access or modify files outside this directory. -3. The system will ensure operations are within this directory. +2. All file operations are restricted to the 'projects' directory for security reasons. You cannot access or modify files above this directory. +3. The system will ensure operations are within this directory. Do not create a projects folder as you already start out in the projects folder. If asked to make an app create a new folder if needed and add files inside that folder. 4. After using a tool, report the result and ask if further actions are needed. 5. For uploaded files, analyze the contents immediately without using the read_file tool. 6. In auto mode, iterate through tasks autonomously, providing regular progress updates.