Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support PyGame Sandbox #5

Merged
merged 1 commit into from
Nov 18, 2024
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
32 changes: 29 additions & 3 deletions fastchat/serve/gradio_block_arena_named.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
get_model_description_md,
)
from fastchat.serve.remote_logger import get_remote_logger
from fastchat.serve.sandbox.code_runner import DEFAULT_SANDBOX_INSTRUCTION, create_chatbot_sandbox_state, on_click_run_code, update_sandbox_config
from fastchat.serve.sandbox.code_runner import DEFAULT_SANDBOX_INSTRUCTIONS, SUPPORTED_SANDBOX_ENVIRONMENTS, create_chatbot_sandbox_state, on_click_run_code, update_sandbox_config
from fastchat.utils import (
build_logger,
moderation_filter,
Expand Down Expand Up @@ -427,13 +427,39 @@ def build_side_by_side_ui_named(models):
with gr.Group():
with gr.Row():
enable_sandbox_checkbox = gr.Checkbox(value=False, label="Enable Sandbox", interactive=True)
sandbox_env_choice = gr.Dropdown(choices=["React", "Auto"], label="Sandbox Environment", interactive=True)
sandbox_env_choice = gr.Dropdown(choices=SUPPORTED_SANDBOX_ENVIRONMENTS, label="Sandbox Environment", interactive=True)
with gr.Group():
with gr.Accordion("Sandbox Instructions", open=False):
sandbox_instruction_textarea = gr.TextArea(
value=DEFAULT_SANDBOX_INSTRUCTION
value=''
)

sandbox_env_choice.change(
fn=lambda env: DEFAULT_SANDBOX_INSTRUCTIONS[env],
inputs=[sandbox_env_choice],
outputs=[sandbox_instruction_textarea]
).then(
fn=update_sandbox_config,
inputs=[
enable_sandbox_checkbox,
sandbox_env_choice,
sandbox_instruction_textarea,
*sandbox_states
],
outputs=[*sandbox_states]
)

sandbox_instruction_textarea.change(
fn=update_sandbox_config,
inputs=[
enable_sandbox_checkbox,
sandbox_env_choice,
sandbox_instruction_textarea,
*sandbox_states
],
outputs=[*sandbox_states]
)

# update sandbox global config
enable_sandbox_checkbox.change(
fn=update_sandbox_config,
Expand Down
110 changes: 105 additions & 5 deletions fastchat/serve/sandbox/code_runner.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
API key for the e2b API.
'''

SUPPORTED_SANDBOX_ENVIRONMENTS = ['React', 'PyGame', 'Auto']

VALID_GRADIO_CODE_LANGUAGES = ['python', 'c', 'cpp', 'markdown', 'json', 'html', 'css', 'javascript', 'jinja2', 'typescript', 'yaml', 'dockerfile', 'shell', 'r', 'sql',
'sql-msSQL', 'sql-mySQL', 'sql-mariaDB', 'sql-sqlite', 'sql-cassandra', 'sql-plSQL', 'sql-hive', 'sql-pgSQL', 'sql-gql', 'sql-gpSQL', 'sql-sparkSQL', 'sql-esper']
Expand All @@ -28,11 +29,62 @@
Button in the chat to run the code in the sandbox.
'''

DEFAULT_SANDBOX_INSTRUCTION = "Generate typescript for a single-file react component tsx file. Do not use external libs or import external files. Surround code with ``` in markdown."
DEFAULT_REACT_SANDBOX_INSTRUCTION = "Generate typescript for a single-file react component tsx file. Do not use external libs or import external files. Surround code with ``` in markdown."
'''
Default sandbox prompt instruction.
'''

DEFAULT_PYGAME_SANDBOX_INSTRUCTION = (
'''
Generate a pygame code snippet for a single file. Surround code with ``` in markdown.
Write pygame main method in a sync function like:
```python
import asyncio
import pygame

pygame.init()

def menu(events):
# draw
# check events
# change state
pass


def play(events):
# draw
# check events
# change state
pass

game_state = menu

async def main():
global game_state

# You can initialise pygame here as well

while game_state:
game_state(pygame.event.get())
pygame.display.update()
await asyncio.sleep(0) # do not forget that one, it must be called on every frame

# Closing the game (not strictly required)
pygame.quit()
sys.exit()

if __name__ == "__main__":
asyncio.run(main())
```
'''
)

DEFAULT_SANDBOX_INSTRUCTIONS = {
"Auto": "Auto-detect the code language and run in the appropriate sandbox.",
"React": DEFAULT_REACT_SANDBOX_INSTRUCTION,
"PyGame": DEFAULT_PYGAME_SANDBOX_INSTRUCTION,
}


class ChatbotSandboxState(TypedDict):
'''
Expand Down Expand Up @@ -65,8 +117,8 @@ def update_sandbox_config(
'''
for state in states:
state["enable_sandbox"] = enable_sandbox
state["sandbox_environment"] = sandbox_environment
state["sandbox_instruction"] = sandbox_instruction
state["sandbox_environment"] = sandbox_environment
return list(states)


Expand Down Expand Up @@ -162,8 +214,6 @@ def run_code_interpreter(code: str, code_language: str | None) -> tuple[str, str
results.append(rendered_result)
return output, "\n".join(results), js_code



def run_react_sandbox(code: str) -> str:
"""
Executes the provided code within a sandboxed environment and returns the output.
Expand Down Expand Up @@ -191,6 +241,44 @@ def run_react_sandbox(code: str) -> str:
sandbox_url = 'https://' + sandbox.get_host(3000)
return sandbox_url

def run_pygame_sandbox(code: str) -> str:
"""
Executes the provided code within a sandboxed environment and returns the output.

Args:
code (str): The code to be executed.

Returns:
url for remote sandbox
"""
sandbox = Sandbox(
api_key=E2B_API_KEY,
)

sandbox.files.make_dir('mygame')
file_path = "~/mygame/main.py"
sandbox.files.write(path=file_path, data=code, request_timeout=60)

sandbox.commands.run("pip install pygame pygbag black",
timeout=60 * 3,
# on_stdout=lambda message: print(message),
on_stderr=lambda message: print(message),)

# build the pygame code
sandbox.commands.run(
"pygbag --build ~/mygame", # build game
timeout=60 * 5,
# on_stdout=lambda message: print(message),
# on_stderr=lambda message: print(message),
)

process = sandbox.commands.run("python -m http.server 3000", background=True) # start http server

# get game server url
host = sandbox.get_host(3000)
url = f"https://{host}"
return url + '/mygame/build/web/'

def on_click_run_code(
state,
sandbox_state: ChatbotSandboxState,
Expand Down Expand Up @@ -227,7 +315,7 @@ def on_click_run_code(
gr.Code(value=code, language=code_language, visible=True),
)

if is_web_page:
if sandbox_state['sandbox_environment'] == 'React':
url = run_react_sandbox(code)
yield (
gr.Markdown(value="### Running Sandbox", visible=True),
Expand All @@ -239,6 +327,18 @@ def on_click_run_code(
),
gr.skip(),
)
elif sandbox_state['sandbox_environment'] == 'PyGame':
url = run_pygame_sandbox(code)
yield (
gr.Markdown(value="### Running Sandbox", visible=True),
SandboxComponent(
value=(url, code),
label="Example",
visible=True,
key="newsandbox",
),
gr.skip(),
)
else:
output, results, js_code = run_code_interpreter(
code=code, code_language=code_language)
Expand Down