ScratchAI / app.py
szili2011's picture
Create app.py
6686e9d verified
import gradio as gr
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer
from scratch3 import Project
# Load the LLaMa model and tokenizer
model_name = "TheBloke/LLaMa-7B-GGML"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(model_name, device_map="auto")
# Define extended Scratch blocks
scratch_blocks = {
"motion": [
{"name": "move", "parameters": {"steps": "number"}},
{"name": "turn", "parameters": {"angle": "number"}},
{"name": "go to", "parameters": {"x": "number", "y": "number"}},
{"name": "point in direction", "parameters": {"direction": "number"}}
],
"sound": [
{"name": "play sound", "parameters": {"sound": "string"}},
{"name": "stop all sounds", "parameters": {}}
],
"event": [
{"name": "when green flag clicked", "parameters": {}},
{"name": "when this sprite clicked", "parameters": {}}
],
"control": [
{"name": "wait", "parameters": {"seconds": "number"}},
{"name": "repeat", "parameters": {"times": "number"}},
{"name": "if then", "parameters": {"condition": "string"}},
],
"variables": [
{"name": "set variable", "parameters": {"variable": "string", "value": "number"}},
{"name": "change variable", "parameters": {"variable": "string", "value": "number"}},
],
"looks": [
{"name": "say", "parameters": {"message": "string"}},
{"name": "think", "parameters": {"message": "string"}},
{"name": "show", "parameters": {}},
{"name": "hide", "parameters": {}}
],
"drawing": [
{"name": "draw line", "parameters": {"x1": "number", "y1": "number", "x2": "number", "y2": "number"}},
{"name": "draw circle", "parameters": {"x": "number", "y": "number", "radius": "number"}},
{"name": "draw rectangle", "parameters": {"x": "number", "y": "number", "width": "number", "height": "number"}},
]
}
def generate_script(prompt):
"""
Generate a Scratch script based on the provided prompt using the AI model.
"""
inputs = tokenizer(prompt, return_tensors="pt").to(model.device)
with torch.no_grad():
outputs = model.generate(inputs["input_ids"], max_length=300, num_return_sequences=1)
generated_script = tokenizer.decode(outputs[0], skip_special_tokens=True)
return generated_script
def parse_generated_script(generated_script):
"""
Parse the generated script into instructions.
"""
instructions = []
for line in generated_script.split('.'):
line = line.strip()
if line:
instructions.append(line)
return instructions
def create_scratch_project(title, sprites, sounds, custom_prompt):
"""
Create a Scratch project based on user input and generated script.
"""
# Combine title and custom prompt for better context
prompt = f"Create a Scratch project titled '{title}' featuring sprites {sprites} with sounds {sounds}. " \
f"Additional details: {custom_prompt}"
generated_script = generate_script(prompt)
# Parse the generated script into instructions
instructions = parse_generated_script(generated_script)
# Create a new Scratch project
project = Project()
# Add sprites
sprite_list = [sprite.strip() for sprite in sprites.split(',')]
for sprite in sprite_list:
sprite_block = {
"objName": sprite,
"skin": 1,
"x": 0,
"y": 0,
"direction": 90,
"visible": True,
"size": 100
}
project.sprites.append(sprite_block)
# Add sounds if provided
for sound in sounds.split(','):
sound = sound.strip()
if sound:
sound_block = {
"name": sound,
"path": f"sounds/{sound}.wav"
}
project.sounds.append(sound_block)
# Map parsed instructions to Scratch blocks
for instruction in instructions:
if "move" in instruction:
steps = extract_number(instruction)
project.add_motion_block({"name": "move", "parameters": {"steps": steps}})
elif "turn" in instruction:
angle = extract_number(instruction)
project.add_motion_block({"name": "turn", "parameters": {"angle": angle}})
elif "play sound" in instruction:
project.add_sound_block({"name": "play sound", "parameters": {"sound": sounds.split(',')[0].strip()}})
elif "when green flag clicked" in instruction:
project.add_event_block({"name": "when green flag clicked"})
elif "repeat" in instruction:
times = extract_number(instruction)
project.add_control_block({"name": "repeat", "parameters": {"times": times}})
elif "say" in instruction:
message = extract_message(instruction)
project.add_looks_block({"name": "say", "parameters": {"message": message}})
elif "draw line" in instruction:
x1, y1, x2, y2 = extract_drawing_parameters(instruction)
project.add_drawing_block({"name": "draw line", "parameters": {"x1": x1, "y1": y1, "x2": x2, "y2": y2}})
elif "draw circle" in instruction:
x, y, radius = extract_circle_parameters(instruction)
project.add_drawing_block({"name": "draw circle", "parameters": {"x": x, "y": y, "radius": radius}})
elif "draw rectangle" in instruction:
x, y, width, height = extract_rectangle_parameters(instruction)
project.add_drawing_block({"name": "draw rectangle", "parameters": {"x": x, "y": y, "width": width, "height": height}})
elif "remove sprite" in instruction:
sprite_name = extract_sprite_name(instruction)
project.remove_sprite(sprite_name)
elif "rename sprite" in instruction:
old_name, new_name = extract_rename_sprite_parameters(instruction)
project.rename_sprite(old_name, new_name)
# Save the project as an .sb3 file
sb3_file_path = f"{title}.sb3"
with open(sb3_file_path, "wb") as f:
f.write(project.export())
return sb3_file_path
def extract_number(instruction):
"""
Extract a number from the instruction string.
"""
import re
match = re.search(r'\d+', instruction)
return int(match.group(0)) if match else 0
def extract_message(instruction):
"""
Extract a message from the instruction string.
"""
return instruction.split("say")[-1].strip().strip('"')
def extract_drawing_parameters(instruction):
"""
Extract parameters for drawing a line from the instruction string.
"""
numbers = list(map(int, re.findall(r'\d+', instruction)))
return numbers
def extract_circle_parameters(instruction):
"""
Extract parameters for drawing a circle from the instruction string.
"""
numbers = list(map(int, re.findall(r'\d+', instruction)))
return numbers
def extract_rectangle_parameters(instruction):
"""
Extract parameters for drawing a rectangle from the instruction string.
"""
numbers = list(map(int, re.findall(r'\d+', instruction)))
return numbers
def extract_sprite_name(instruction):
"""
Extract the sprite name from the instruction string for removal.
"""
return instruction.split("remove sprite")[-1].strip()
def extract_rename_sprite_parameters(instruction):
"""
Extract old and new names for renaming a sprite from the instruction string.
"""
parts = instruction.split("rename sprite")[-1].strip().split("to")
old_name = parts[0].strip()
new_name = parts[1].strip() if len(parts) > 1 else None
return old_name, new_name
# Create Gradio interface
iface = gr.Interface(
fn=create_scratch_project,
inputs=[
gr.Textbox(label="Project Title"),
gr.Textbox(label="Sprite Names (comma separated)"),
gr.Textbox(label="Sound Names (comma separated)"),
gr.Textbox(label="Custom Prompt (optional)"),
],
outputs=gr.File(label="Download Your Scratch Project (.sb3)"),
title="Scratch Project Generator",
description="Create a Scratch project using AI. Provide a title, sprites, sounds, and a custom prompt."
)
# Launch the Gradio app
iface.launch()