From 91e2a6de01f58ca46449d046ab12425b94cca0c3 Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 06:05:04 +0000 Subject: [PATCH 01/10] Testing URL Former-commit-id: 7562c55bcfb8d824d6f72705e14f42740c5761c7 Former-commit-id: 7155ba7d42ab63628a342ae70eca88e4d0791110 Former-commit-id: 04f9fe01d619272767708bd7a7fc30b210a6d63e [formerly 00e0a9177d8acc1936d80efb50c3cf64b6dbbfae] Former-commit-id: 3cf96647c6903790ed82d2a6fcccc2da2fb39e87 --- interpreter/interpreter.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py index bf0c878156..b190d7d695 100644 --- a/interpreter/interpreter.py +++ b/interpreter/interpreter.py @@ -114,7 +114,8 @@ def get_info_for_system_message(self): query = query[-2000:] # Use them to query Open Procedures - url = f"https://open-procedures.replit.app/search/?query={query}" + #url = f"https://open-procedures.replit.app/search/?query={query}" + url = f"https://open-procedures.killianlucas1.repl.co/?query={query}" relevant_procedures = requests.get(url).json()["procedures"] info += "\n\n# Potentially Helpful Procedures (may or may not be related)\n" + "\n---\n".join(relevant_procedures) From 1ecabf802c168cd722472aacc68d8c1434d1d443 Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 06:08:37 +0000 Subject: [PATCH 02/10] Testing Open Procedures URL Former-commit-id: 7d075f27150987e539010a0cf8bab29b126f8a26 Former-commit-id: e6391216b33d3e4074ad95fd127fac9b07ba145d Former-commit-id: 08256a37d0388768fbb195c907109f4aabe6424e [formerly b555b74f44753935dc31260a0998afd4f2c02340] Former-commit-id: 293b5878490f4ad320aa500a53b9675a61a2de40 --- interpreter/interpreter.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py index b190d7d695..6fc8ee7de4 100644 --- a/interpreter/interpreter.py +++ b/interpreter/interpreter.py @@ -116,7 +116,11 @@ def get_info_for_system_message(self): # Use them to query Open Procedures #url = f"https://open-procedures.replit.app/search/?query={query}" url = f"https://open-procedures.killianlucas1.repl.co/?query={query}" - relevant_procedures = requests.get(url).json()["procedures"] + relevant_procedures = requests.get(url) + print(relevant_procedures) + relevant_procedures = relevant_procedures.json() + print(relevant_procedures) + relevant_procedures = relevant_procedures["procedures"] info += "\n\n# Potentially Helpful Procedures (may or may not be related)\n" + "\n---\n".join(relevant_procedures) elif self.local: From 0c21a09b13bcfed41c495660bc7acc245332abc3 Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 06:11:18 +0000 Subject: [PATCH 03/10] Testing Open Procedures URL Former-commit-id: 63e92b2b8df3a9ddc2782a8a7632924f5ce113f7 Former-commit-id: ac5116ad36f9169e503b98dc841f4f1ed269faa4 Former-commit-id: ff4fef53d609fbb15c83c83d2f4d729fdc2fb3f9 [formerly 36d3766e8855adbe7e98cc0797be22b6e49f6364] Former-commit-id: fd670fa6dd36ddbbc7a7c57de025e8686789f61f --- interpreter/interpreter.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py index 6fc8ee7de4..74522ad09a 100644 --- a/interpreter/interpreter.py +++ b/interpreter/interpreter.py @@ -114,13 +114,9 @@ def get_info_for_system_message(self): query = query[-2000:] # Use them to query Open Procedures - #url = f"https://open-procedures.replit.app/search/?query={query}" - url = f"https://open-procedures.killianlucas1.repl.co/?query={query}" - relevant_procedures = requests.get(url) - print(relevant_procedures) - relevant_procedures = relevant_procedures.json() - print(relevant_procedures) - relevant_procedures = relevant_procedures["procedures"] + # url = f"https://open-procedures.replit.app/search/?query={query}" # <-- Prod URL + url = f"https://open-procedures.killianlucas1.repl.co/search/?query={query}" # <-- Test URL + relevant_procedures = requests.get(url).json()["procedures"] info += "\n\n# Potentially Helpful Procedures (may or may not be related)\n" + "\n---\n".join(relevant_procedures) elif self.local: From 1c3165b85ff3f773dc8ef6e937143b62e4567b08 Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:12:03 +0000 Subject: [PATCH 04/10] HTML Support Former-commit-id: 8f1801a82fec045fe57801394704f82f2927fd22 Former-commit-id: 70e5b09259819e728138994c71c0ba9bc5939905 Former-commit-id: bfac103929788ca6b061a52c5e909ddc8ec65f00 [formerly fdb2a1be3df38b8a711b83aabd3aded7249eb25c] Former-commit-id: fb8dc0532b5279ccaf60c67ac62aad1736543ccf --- interpreter/code_interpreter.py | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/interpreter/code_interpreter.py b/interpreter/code_interpreter.py index 5f1dfc3695..df36b0f1da 100644 --- a/interpreter/code_interpreter.py +++ b/interpreter/code_interpreter.py @@ -1,4 +1,6 @@ import subprocess +import webbrowser +import tempfile import threading import traceback import platform @@ -9,7 +11,17 @@ import os import re -# Mapping of languages to their start and print commands + +def run_html(html_content): + # Create a temporary HTML file with the content + with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as f: + f.write(html_content.encode()) + + # Open the HTML file with the default web browser + webbrowser.open('file://' + os.path.realpath(f.name)) + + +# Mapping of languages to their start, run, and print commands language_map = { "python": { # Python is run from this interpreter with sys.executable @@ -32,6 +44,10 @@ # (We'll prepend "osascript -e" every time, not once at the start, so we want an empty shell) "start_cmd": os.environ.get('SHELL', '/bin/zsh'), "print_cmd": 'log "{}"' + }, + "html": { + "open_subrocess" = False, + "run_function": run_html, } } @@ -105,8 +121,11 @@ def run(self): return message """ + # Should we keep a subprocess open? True by default + open_subrocess = language_map[self.language].get("open_subrocess", True) + # Start the subprocess if it hasn't been started - if not self.proc: + if not self.proc and open_subrocess: try: self.start_process() except: @@ -127,11 +146,12 @@ def run(self): self.output = "" # Use the print_cmd for the selected language - self.print_cmd = language_map[self.language]["print_cmd"] + self.print_cmd = language_map[self.language].get("print_cmd") code = self.code # Add print commands that tell us what the active line is - code = self.add_active_line_prints(code) + if self.print_cmd: + code = self.add_active_line_prints(code) # If it's Python, we also need to prepare it for `python -i` if self.language == "python": @@ -161,7 +181,8 @@ def run(self): code = "\n".join(code_lines) # Add end command (we'll be listening for this so we know when it ends) - code += "\n\n" + self.print_cmd.format('END_OF_EXECUTION') + if self.print_cmd: + code += "\n\n" + self.print_cmd.format('END_OF_EXECUTION') # Applescript-specific processing if self.language == "applescript": @@ -171,13 +192,18 @@ def run(self): code = '"' + code + '"' # Prepend start command code = "osascript -e " + code - + # Debug if self.debug_mode: print("Running code:") print(code) print("---") + # HTML-specific processing + if self.language == "html": + language_map["html"]["run_function"](code) + return "Opened successfully." + # Reset self.done so we can .wait() for it self.done = threading.Event() self.done.clear() From 98bfcc2784816bfba27b018dcf1e8b2930bbb7fd Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:12:43 +0000 Subject: [PATCH 05/10] HTML Support Former-commit-id: 2ca308626c0767d0d8b79175ff22469a12627085 Former-commit-id: fed13560ac126de3005d08668852dd4372e8a947 Former-commit-id: 42d27926fd677fb6fabc239a0ed25a128500bf5e [formerly 8375ed49f16a28e8987d54d5bc2f7e94980a115d] Former-commit-id: 025a1b21d06fe764018e0e90c38252b5d030c4e3 --- interpreter/interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py index 74522ad09a..bee1992f2c 100644 --- a/interpreter/interpreter.py +++ b/interpreter/interpreter.py @@ -28,7 +28,7 @@ "type": "string", "description": "The programming language.", - "enum": ["python", "shell", "applescript", "javascript"] + "enum": ["python", "shell", "applescript", "javascript", "html"] }, "code": { "type": "string", From e3eed4bff579b40e5b0d85b1a22556a946e5efea Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 18:14:51 +0000 Subject: [PATCH 06/10] HTML Support Former-commit-id: 253c44279e14f5514dabc92f65abbaf29f1c2aff Former-commit-id: 48c0889c607c2dbe0d8bb79d34166c88ea2ec18c Former-commit-id: e8af317fecc83fad54a9e3cf85b2cab09b1d3308 [formerly 9a4e1ca01ca789a5d5c108d93d15ea1f3158610b] Former-commit-id: 5a486e89a7935cd0287d52878ad69f7ad285bc5b --- interpreter/code_interpreter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interpreter/code_interpreter.py b/interpreter/code_interpreter.py index df36b0f1da..1f8aebe8f9 100644 --- a/interpreter/code_interpreter.py +++ b/interpreter/code_interpreter.py @@ -46,7 +46,7 @@ def run_html(html_content): "print_cmd": 'log "{}"' }, "html": { - "open_subrocess" = False, + "open_subrocess": False, "run_function": run_html, } } From 94f415a528fc8980ce08edb459bb6fd821ea106b Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sat, 12 Aug 2023 19:00:39 +0000 Subject: [PATCH 07/10] HTML Support Former-commit-id: c853ea353fab390ae1d8fce1af7bd3273c2b6303 Former-commit-id: 098f3f2abb59a1e860f993dcc5856d40df529486 Former-commit-id: 24b5fc8f0298840baa3a1a2f05bf19e29a48e6b6 [formerly c277e0a5f73d0b7fea93ecde304dc55b83ab3c05] Former-commit-id: d75a9a510806138597fe8763ec8bee734eca094b --- interpreter/code_interpreter.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/interpreter/code_interpreter.py b/interpreter/code_interpreter.py index 1f8aebe8f9..0702485e0d 100644 --- a/interpreter/code_interpreter.py +++ b/interpreter/code_interpreter.py @@ -20,6 +20,8 @@ def run_html(html_content): # Open the HTML file with the default web browser webbrowser.open('file://' + os.path.realpath(f.name)) + return f"Saved to {os.path.realpath(f.name)} and opened with the user's default web browser." + # Mapping of languages to their start, run, and print commands language_map = { @@ -201,8 +203,8 @@ def run(self): # HTML-specific processing if self.language == "html": - language_map["html"]["run_function"](code) - return "Opened successfully." + output = language_map["html"]["run_function"](code) + return output # Reset self.done so we can .wait() for it self.done = threading.Event() From 633640f373eca4585d7bf91b32713fe9c9b323a9 Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sun, 13 Aug 2023 03:30:44 +0000 Subject: [PATCH 08/10] Fixed python interpreter Former-commit-id: 3b1d2b1bef9d71820d209f1ffc92760428e735f5 Former-commit-id: 3c4a1fe5705cbf949562489919678db33b3ff934 Former-commit-id: b0404502e10f2ca3f69c24f7163e0958e9368332 [formerly 4dd401c2656b98d33fc42b4a6119ba8af5587a61] Former-commit-id: 9fbc14167d19378c939eb0f533fc1ac2e5134df7 --- interpreter/cli.py | 3 +- interpreter/code_interpreter.py | 71 +++++++++++++++++++++++---------- 2 files changed, 51 insertions(+), 23 deletions(-) diff --git a/interpreter/cli.py b/interpreter/cli.py index 38149597c7..8f7e3d76e9 100644 --- a/interpreter/cli.py +++ b/interpreter/cli.py @@ -21,7 +21,8 @@ def cli(interpreter): '--local', action='store_true', help='run fully local with llama-2') - parser.add_argument('--debug', + parser.add_argument('-d', + '--debug', action='store_true', help='debug mode. prints extra information') args = parser.parse_args() diff --git a/interpreter/code_interpreter.py b/interpreter/code_interpreter.py index 0702485e0d..52a6876874 100644 --- a/interpreter/code_interpreter.py +++ b/interpreter/code_interpreter.py @@ -82,7 +82,8 @@ def start_process(self): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True) + text=True, + bufsize=0) # Start watching ^ its `stdout` and `stderr` streams threading.Thread(target=self.save_and_display_stream, @@ -308,6 +309,12 @@ def save_and_display_stream(self, stream): # Remove trailing ">"s line = re.sub(r'^\s*(>\s*)+', '', line) + # Python's interactive REPL outputs a million things + # So we clean it up: + if self.language == "python": + if re.match(r'^(\s*>>>\s*|\s*\.\.\.\s*)', line): + continue + # Check if it's a message we added (like ACTIVE_LINE) # Or if we should save it to self.output if line.startswith("ACTIVE_LINE:"): @@ -376,10 +383,16 @@ def insert_print_statement(self, line_number): def process_body(self, body): """Processes a block of statements, adding print calls.""" new_body = [] + + # In case it's not iterable: + if not isinstance(body, list): + body = [body] + for sub_node in body: if hasattr(sub_node, 'lineno'): new_body.append(self.insert_print_statement(sub_node.lineno)) new_body.append(sub_node) + return new_body def visit(self, node): @@ -414,27 +427,41 @@ def add_active_line_prints_to_python(code): def prepare_for_python_interactive(code): """ - Adjusts code formatting for the python -i flag. It adds newlines based - on whitespace to make code work in interactive mode. + Adjusts code formatting for the python -i flag. """ + return wrap_in_try_except(code) + +def wrap_in_try_except(code): + # Add import traceback + code = "import traceback\n" + code + + # Parse the input code into an AST + parsed_code = ast.parse(code) + + # Wrap the entire code's AST in a single try-except block + try_except = ast.Try( + body=parsed_code.body, + handlers=[ + ast.ExceptHandler( + type=ast.Name(id="Exception", ctx=ast.Load()), + name=None, + body=[ + ast.Expr( + value=ast.Call( + func=ast.Attribute(value=ast.Name(id="traceback", ctx=ast.Load()), attr="print_exc", ctx=ast.Load()), + args=[], + keywords=[] + ) + ), + ] + ) + ], + orelse=[], + finalbody=[] + ) - def get_indentation(line): - """Returns the number of leading spaces in a line, treating 4 spaces as one level of indentation.""" - return len(line) - len(line.lstrip()) - - lines = code.split('\n') - adjusted_code = [] - - previous_indentation = 0 - - for line in lines: - current_indentation = get_indentation(line) - - if current_indentation < previous_indentation: - if not (line.strip().startswith("except:") or line.strip().startswith("else:") or line.strip().startswith("elif:") or line.strip().startswith("finally:")): - adjusted_code.append('') # end of block - - adjusted_code.append(line) - previous_indentation = current_indentation + # Assign the try-except block as the new body + parsed_code.body = [try_except] - return '\n'.join(adjusted_code) \ No newline at end of file + # Convert the modified AST back to source code + return ast.unparse(parsed_code) From b8c8da12e4d8c322770c84bdb79db44ed67a945b Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sun, 13 Aug 2023 03:42:19 +0000 Subject: [PATCH 09/10] Fixed multiline strings not rendering in realtime Former-commit-id: 5fb40497ded757abfd540c1138795d9b7f0bbe8f Former-commit-id: aaf890fa9daac73d3e995ab84c652223d28f5015 Former-commit-id: 8b375ab3b182ca00a4f5479e2ba781f409975c1d [formerly 74dbd91733804659f25b07961708d52ef90fa571] Former-commit-id: fedb300380fe42befb96a24db7c3db808fbc14a2 --- interpreter/utils.py | 69 +++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 45 deletions(-) diff --git a/interpreter/utils.py b/interpreter/utils.py index 04c75fe016..096986a55f 100644 --- a/interpreter/utils.py +++ b/interpreter/utils.py @@ -32,67 +32,46 @@ def escape_newlines_in_json_string_values(s): return ''.join(result) def parse_partial_json(s): - """ - Tries to parse a string as JSON and if it fails, attempts to 'close' any open JSON structures. - - Parameters: - s (str): The string to parse as JSON. - - Returns: - json: The parsed JSON if successful, or None if it fails even after attempting to close open structures. - """ - - # First, try to parse the string as-is. If it's valid JSON, we'll return it directly. - try: - return json.loads(s) - except json.JSONDecodeError: - pass # The string is not valid JSON. We'll try to handle this case below. - - # First, make sure newlines inside double quotes are escaped properly (a common error in GPT function calls) - s = escape_newlines_in_json_string_values(s) - - # Initialize a stack to keep track of open braces and brackets. + # Initialize a stack to keep track of open braces, brackets, and strings. stack = [] - - # Initialize a flag to keep track of whether we're currently inside a string. is_inside_string = False + escaped = False # Process each character in the string one at a time. for char in s: - - # Handle quotes, which denote the start or end of a string in JSON. - if char == '"': - - if stack and stack[-1] == '\\': # <- This is a single backslash, even though it looks like two! - - # This quote is escaped, so it doesn't affect whether we're inside a string. - stack.pop() + if is_inside_string: + if char == '"' and not escaped: + is_inside_string = False + elif char == '\\': + escaped = not escaped else: - # This quote is not escaped, so it toggles whether we're inside a string. - is_inside_string = not is_inside_string - - # If we're not inside a string, we need to handle braces and brackets. - elif not is_inside_string: - if char == '{' or char == '[': - # This character opens a new structure, so add it to the stack. - stack.append(char) + escaped = False + else: + if char == '"': + is_inside_string = True + escaped = False + elif char == '{': + stack.append('}') + elif char == '[': + stack.append(']') elif char == '}' or char == ']': - # This character closes a structure, so remove the most recently opened structure from the stack. - if stack: + if stack and stack[-1] == char: stack.pop() + else: + # Mismatched closing character; the input is malformed. + return None # If we're still inside a string at the end of processing, we need to close the string. if is_inside_string: s += '"' # Close any remaining open structures in the reverse order that they were opened. - while stack: - open_char = stack.pop() - s += '}' if open_char == '{' else ']' + for closing_char in reversed(stack): + s += closing_char - # Attempt to parse the string as JSON again now that we've closed all open structures. + # Attempt to parse the modified string as JSON. try: return json.loads(s) except json.JSONDecodeError: # If we still can't parse the string as JSON, return None to indicate failure. - return None \ No newline at end of file + return None From 19838aacb35393518afc5832d6477ab031119219 Mon Sep 17 00:00:00 2001 From: KillianLucas <63927363+KillianLucas@users.noreply.github.com> Date: Sun, 13 Aug 2023 09:03:39 +0000 Subject: [PATCH 10/10] Fixed python interpreter edge cases Former-commit-id: 3c2bd5de9ad20ba3d099a81cfa7e770d99740dd5 Former-commit-id: 7c99ca7c1df4786d0df3689872dee779dc0e4343 Former-commit-id: d68f03bb827f67ad3ac1a47996d792b36a16fe02 [formerly 463556ac22d9e071918d1eefacdcb179f8e3feb2] Former-commit-id: 0bd73f96d7032cb575496e5d723dc127325e634b --- interpreter/code_interpreter.py | 42 ++++++++------------------------- interpreter/interpreter.py | 3 +-- 2 files changed, 11 insertions(+), 34 deletions(-) diff --git a/interpreter/code_interpreter.py b/interpreter/code_interpreter.py index 52a6876874..9d556555ac 100644 --- a/interpreter/code_interpreter.py +++ b/interpreter/code_interpreter.py @@ -154,14 +154,8 @@ def run(self): # Add print commands that tell us what the active line is if self.print_cmd: - code = self.add_active_line_prints(code) - - # If it's Python, we also need to prepare it for `python -i` - if self.language == "python": - - # Normalize code by parsing then unparsing it try: - code = prepare_for_python_interactive(code) + code = self.add_active_line_prints(code) except: # If this failed, it means the code didn't compile # This traceback will be our output. @@ -169,16 +163,20 @@ def run(self): traceback_string = traceback.format_exc() self.output = traceback_string self.update_active_block() - + # Before you return, wait for the display to catch up? # (I'm not sure why this works) time.sleep(0.1) - + return self.output - - code = fix_code_indentation(code) + + if self.language == "python": + # This lets us stop execution when error happens (which is not default -i behavior) + # And solves a bunch of indentation problems-- if everything's indented, -i treats it as one block + code = wrap_in_try_except(code) # Remove any whitespace lines, as this will break indented blocks + # (are we sure about this? test this) code_lines = code.split("\n") code_lines = [c for c in code_lines if c.strip() != ""] code = "\n".join(code_lines) @@ -202,7 +200,7 @@ def run(self): print(code) print("---") - # HTML-specific processing + # HTML-specific processing (and running) if self.language == "html": output = language_map["html"]["run_function"](code) return output @@ -330,20 +328,6 @@ def save_and_display_stream(self, stream): self.update_active_block() -def fix_code_indentation(code): - lines = code.split("\n") - fixed_lines = [] - was_indented = False - for line in lines: - current_indent = len(line) - len(line.lstrip()) - if current_indent == 0 and was_indented: - fixed_lines.append('') # Add an empty line after an indented block - fixed_lines.append(line) - was_indented = current_indent > 0 - - return "\n".join(fixed_lines) - - def truncate_output(data): # In the future, this will come from a config file @@ -425,12 +409,6 @@ def add_active_line_prints_to_python(code): new_tree = transformer.visit(tree) return ast.unparse(new_tree) -def prepare_for_python_interactive(code): - """ - Adjusts code formatting for the python -i flag. - """ - return wrap_in_try_except(code) - def wrap_in_try_except(code): # Add import traceback code = "import traceback\n" + code diff --git a/interpreter/interpreter.py b/interpreter/interpreter.py index bee1992f2c..f3edc7a6d7 100644 --- a/interpreter/interpreter.py +++ b/interpreter/interpreter.py @@ -114,8 +114,7 @@ def get_info_for_system_message(self): query = query[-2000:] # Use them to query Open Procedures - # url = f"https://open-procedures.replit.app/search/?query={query}" # <-- Prod URL - url = f"https://open-procedures.killianlucas1.repl.co/search/?query={query}" # <-- Test URL + url = f"https://open-procedures.replit.app/search/?query={query}" relevant_procedures = requests.get(url).json()["procedures"] info += "\n\n# Potentially Helpful Procedures (may or may not be related)\n" + "\n---\n".join(relevant_procedures)