diff --git a/docs/guides/advanced-terminal-usage.mdx b/docs/guides/advanced-terminal-usage.mdx index 745723c6cd..e144638e77 100644 --- a/docs/guides/advanced-terminal-usage.mdx +++ b/docs/guides/advanced-terminal-usage.mdx @@ -9,3 +9,4 @@ Magic commands can be used to control the interpreter's behavior in interactive - `%undo`: Remove the last message and its response - `%save_message [path]`: Save messages to a JSON file - `%load_message [path]`: Load messages from a JSON file +- `%jupyter`: Export the current session to a Jupyter notebook file (.ipynb) to the Downloads folder. diff --git a/interpreter/terminal_interface/magic_commands.py b/interpreter/terminal_interface/magic_commands.py index 03281560f8..8a02a966a8 100644 --- a/interpreter/terminal_interface/magic_commands.py +++ b/interpreter/terminal_interface/magic_commands.py @@ -2,7 +2,9 @@ import os import subprocess import time +import sys +from datetime import datetime from ..core.utils.system_debug_info import system_info from .utils.count_tokens import count_messages_tokens from .utils.display_markdown_message import display_markdown_message @@ -55,6 +57,7 @@ def handle_help(self, arguments): "%tokens [prompt]": "EXPERIMENTAL: Calculate the tokens used by the next request based on the current conversation's messages and estimate the cost of that request; optionally provide a prompt to also calulate the tokens used by that prompt and the total amount of tokens that will be sent with the next request", "%help": "Show this help message.", "%info": "Show system and interpreter information", + "%jupyter": "Export the conversation to a Jupyter notebook file", } base_message = ["> **Available Commands:**\n\n"] @@ -172,6 +175,81 @@ def handle_count_tokens(self, prompt): display_markdown_message("\n".join(outputs)) +def get_downloads_path(): + if os.name == 'nt': + # For Windows + downloads = os.path.join(os.environ['USERPROFILE'], 'Downloads') + else: + # For MacOS and Linux + downloads = os.path.join(os.path.expanduser('~'), 'Downloads') + return downloads + +def install_and_import(package): + try: + module = __import__(package) + except ImportError: + try: + # Install the package silently with pip + print("") + print(f"Installing {package}...") + print("") + subprocess.check_call([sys.executable, "-m", "pip", "install", package], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + module = __import__(package) + except subprocess.CalledProcessError: + # If pip fails, try pip3 + try: + subprocess.check_call([sys.executable, "-m", "pip3", "install", package], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL + ) + except subprocess.CalledProcessError: + print(f"Failed to install package {package}.") + return + finally: + globals()[package] = module + return module + +def jupyter(self, arguments): + # Dynamically install nbformat if not already installed + nbformat = install_and_import('nbformat') + from nbformat.v4 import new_notebook, new_code_cell, new_markdown_cell + + downloads = get_downloads_path() + current_time = datetime.now() + formatted_time = current_time.strftime("%m-%d-%y-%I%M%p") + filename = f"open-interpreter-{formatted_time}.ipynb" + notebook_path = os.path.join(downloads, filename) + nb = new_notebook() + cells = [] + + for msg in self.messages: + if msg['role'] == 'user' and msg['type'] == 'message': + # Prefix user messages with '>' to render them as block quotes, so they stand out + content = f"> {msg['content']}" + cells.append(new_markdown_cell(content)) + elif msg['role'] == 'assistant' and msg['type'] == 'message': + cells.append(new_markdown_cell(msg['content'])) + elif msg['type'] == 'code': + # Handle the language of the code cell + if 'format' in msg and msg['format']: + language = msg['format'] + else: + language = 'python' # Default to Python if no format specified + code_cell = new_code_cell(msg['content']) + code_cell.metadata.update({"language": language}) + cells.append(code_cell) + + nb['cells'] = cells + + with open(notebook_path, 'w', encoding='utf-8') as f: + nbformat.write(nb, f) + + print("") + display_markdown_message(f"Jupyter notebook file exported to {os.path.abspath(notebook_path)}") + def handle_magic_command(self, user_input): # Handle shell @@ -191,6 +269,7 @@ def handle_magic_command(self, user_input): "undo": handle_undo, "tokens": handle_count_tokens, "info": handle_info, + "jupyter": jupyter, } user_input = user_input[1:].strip() # Capture the part after the `%`