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

Add make_slides function for creating ODP presentations from JSON. #422

Closed
wants to merge 8 commits into from
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ urllib3==1.26.15
requests-toolbelt==0.10.1
twine
python-gitlab
odfpy
39 changes: 29 additions & 10 deletions src/git_bob/_ai_github_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
AGENT_NAME = os.environ.get("GIT_BOB_AGENT_NAME", "git-bob")
SYSTEM_PROMPT = os.environ.get("SYSTEM_MESSAGE", f"You are an AI-based coding assistant named {AGENT_NAME}. You are an excellent Python programmer and software engineer.")

import json

def setup_ai_remark():
"""
Expand Down Expand Up @@ -247,6 +248,8 @@ def create_or_modify_file(repository, issue, filename, branch_name, issue_summar
format_specific_instructions = " When writing new functions, use numpy-style docstrings."
elif filename.endswith('.ipynb'):
format_specific_instructions = " In the notebook file, write short code snippets in code cells and avoid long code blocks. Make sure everything is done step-by-step and we can inspect intermediate results. Add explanatory markdown cells in front of every code cell. The notebook has NO cell outputs! Make sure that there is code that saves results such as plots, images or dataframes, e.g. as .png or .csv files. Numpy images have to be converted to np.uint8 before saving as .png. Plots must be saved to disk before the cell ends or it is shown. The notebook must be executable from top to bottom without errors. Return the notebook in JSON format!"
elif filename.endswith('.odp'):
format_specific_instructions = "\nDescribe the presentation as a JSON array of slides, each with a title and content. The 'content' should be an array of strings, each representing a content block on the slide."

if Config.git_utilities.check_if_file_exists(repository, branch_name, filename):
file_content = Config.git_utilities.decode_file(Config.git_utilities.get_file_in_repository(repository, branch_name, filename))
Expand Down Expand Up @@ -316,6 +319,18 @@ def create_or_modify_file(repository, issue, filename, branch_name, issue_summar
print("Erasing outputs in generated ipynb file")
new_content = erase_outputs_of_code_cells(new_content)
do_execute_notebook = True
elif filename.endswith('.odp'):
# Specific handling for ODP files
import json
from ._utilities import make_slides

slides_description_json = new_content
make_slides(slides_description_json, filename) # Use previously defined function to create ODP

# Load created ODP into a binary format
with open(filename, 'rb') as f:
new_content = f.read()
os.remove(filename) # Remove temporary file

if do_execute_notebook:
print("Executing the notebook", len(new_content))
Expand Down Expand Up @@ -434,6 +449,7 @@ def solve_github_issue(repository, issue, llm_model, prompt_function, base_branc
- For copies: {{"action": "copy", "old_filename": "...", "new_filename": "..."}}
- For deletions: {{"action": "delete", "filename": "..."}}
- For paintings: {{"action": "paint", "filename": "..."}}
- For presentations: {{"action": "make_slides", "slides_description_json": "..."}}
Respond with the actions as JSON list.
""")

Expand Down Expand Up @@ -471,7 +487,7 @@ def solve_github_issue(repository, issue, llm_model, prompt_function, base_branc
filename = instruction['filename'].strip("/")

created_files = create_or_modify_file(repository, issue, filename, branch_name, discussion,
prompt_function)
prompt_function)
for filename, commit_message in created_files.items():
commit_messages[filename] = commit_message
elif action == 'download':
Expand Down Expand Up @@ -502,6 +518,10 @@ def solve_github_issue(repository, issue, llm_model, prompt_function, base_branc
imagen_prompt = prompt_function("From the following discussion, extract a prompt to paint a picture as discussed:\n\n" + discussion + "\n\nNow extract a prompt for painting a picture as discussed:")
commit_messages[filename] = paint_picture(repository, branch_name, prompt=imagen_prompt, output_filename=filename)

elif action == 'make_slides': # Adding a block for make_slides action
slides_description = instruction['slides_description_json']
make_slides(slides_description)

except Exception as e:
traces = " " + remove_ansi_escape_sequences(traceback.format_exc()).replace("\n", "\n ")
summary = f"""<details>
Expand All @@ -522,7 +542,7 @@ def solve_github_issue(repository, issue, llm_model, prompt_function, base_branc
diffs_prompt = Config.git_utilities.get_diff_of_branches(repository, branch_name, base_branch=base_branch)

# summarize the changes
commit_messages_prompt = "* " + "\n* ".join([f"{k}: {v}" for k,v in commit_messages.items()])
commit_messages_prompt = "* " + "\n* ".join([f"{k}: {v}" for k, v in commit_messages.items()])

file_list = file_list_from_commit_message_dict(repository, branch_name, commit_messages)
file_list_text = ""
Expand Down Expand Up @@ -579,10 +599,10 @@ def solve_github_issue(repository, issue, llm_model, prompt_function, base_branc

try:
Config.git_utilities.send_pull_request(repository,
source_branch=branch_name,
target_branch=base_branch,
title=redact_text(pull_request_title),
description=redact_text(full_report) + f"\n\ncloses #{issue}")
source_branch=branch_name,
target_branch=base_branch,
title=redact_text(pull_request_title),
description=redact_text(full_report) + f"\n\ncloses #{issue}")
except GithubException as e:
Config.git_utilities.add_comment_to_issue(repository, issue, f"{remark}Error creating pull-request: {e}{error_messages}")
except GitlabCreateError as e:
Expand Down Expand Up @@ -635,7 +655,7 @@ def split_issue_in_sub_issues(repository, issue, prompt_function):
from ._github_utilities import create_issue

discussion = Config.git_utilities.get_conversation_on_issue(repository, issue)
ai_remark = setup_ai_remark()+ "\n"
ai_remark = setup_ai_remark() + "\n"

# Implement the prompt to parse the discussion
sub_tasks_json = prompt_function(f"""
Expand Down Expand Up @@ -671,7 +691,7 @@ def split_issue_in_sub_issues(repository, issue, prompt_function):
Write the information down and make a proposal of how to solve the sub-task.
Do not explain your response or anything else. Just respond the relevant information for the sub-task and a potential solution.
""")
body = body.replace(AGENT_NAME, AGENT_NAME[:3]+ "_" + AGENT_NAME[4:]) # prevent endless loops
body = body.replace(AGENT_NAME, AGENT_NAME[:3] + "_" + AGENT_NAME[4:]) # prevent endless loops

issue_number = create_issue(repository, title, ai_remark + body)
sub_issue_numbers.append(issue_number)
Expand All @@ -687,6 +707,7 @@ def split_issue_in_sub_issues(repository, issue, prompt_function):

return sub_issue_numbers


def paint_picture(repository, branch_name, prompt, output_filename="image.png", model="dall-e-3", image_width=1024, image_height=1024, style='vivid', quality='standard'):
"""Generate an image using DALL-E3 based on a prompt and save it to the repository using PIL."""
Log().log(f"-> paint_image({repository}, {branch_name}, ..., {output_filename}, {model}, ...)")
Expand Down Expand Up @@ -722,5 +743,3 @@ def paint_picture(repository, branch_name, prompt, output_filename="image.png",
Config.git_utilities.write_file_in_branch(repository, branch_name, output_filename, img_byte_arr, commit_message=commit_message)

return commit_message


65 changes: 64 additions & 1 deletion src/git_bob/_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,7 +435,7 @@ def clean_output(repository, text):

# if there are strangers tagged, remove those tags
temp = text.split("```")
for i in range(0, len(temp), 2):
for i in 0, len(temp), 2:
temp[i] = temp[i].replace("@", "@ ")
text = "```".join(temp)
contributors = Config.git_utilities.get_contributors(repository)
Expand Down Expand Up @@ -572,3 +572,66 @@ def images_from_url_responses(response, input_shape=None):
return pil_images[0]
else:
return pil_images


def make_slides(slides_description_json, filename="issue_slides.odp"):
"""
Create a presentation from a JSON-encoded slide description and save it as an ODP file.

Parameters
----------
slides_description_json : str
JSON-encoded string containing details of the slides.
filename : str, optional
The name of the output file. Default is 'issue_slides.odp'.

"""
import json
from odf.opendocument import OpenDocumentPresentation
from odf.style import Style, MasterPage, PageLayout, PageLayoutProperties
from odf.text import P
from odf.draw import Page, Frame, TextBox

# Parse json-encoded slide description
slides_data = json.loads(slides_description_json)

# Function to create presentation based on parsed data
presentation = OpenDocumentPresentation()

# Create and add page layout
page_layout = PageLayout(name="MyLayout")
presentation.automaticstyles.addElement(page_layout)

props = PageLayoutProperties(margintop="0cm", marginbottom="0cm", marginleft="0cm", marginright="0cm")
page_layout.addElement(props)

# Create master page
master = MasterPage(name="Standard", pagelayoutname="MyLayout")
presentation.masterstyles.addElement(master)

# Create slides
for slide_data in slides_data:
# Add new slide
slide = Page(masterpagename="Standard")
presentation.presentation.addElement(slide)

# Add title
title_frame = Frame(width="20cm", height="3cm", x="2cm", y="1cm")
slide.addElement(title_frame)
title_box = TextBox()
title_frame.addElement(title_box)
title_box.addElement(P(text=slide_data["title"]))

# Add content columns
num_columns = len(slide_data["content"])
column_width = 16 / num_columns

for i, content in enumerate(slide_data["content"]):
x_pos = 2 + i * column_width
content_frame = Frame(width=f"{column_width}cm", height="5cm", x=f"{x_pos}cm", y="5cm")
slide.addElement(content_frame)
content_box = TextBox()
content_frame.addElement(content_box)
content_box.addElement(P(text=content))

presentation.save(filename)
22 changes: 21 additions & 1 deletion tests/test_utilities.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

def test_remove_outer_markdown():
from git_bob._utilities import remove_outer_markdown
assert remove_outer_markdown("""```python
Expand Down Expand Up @@ -222,3 +221,24 @@ def test_get_modified_files():

assert len(files) == 3


def test_make_slides():
"""Test the make_slides function from the utilities module."""
import json
from git_bob._utilities import make_slides

# Mock JSON slide description
slides_description_json = json.dumps([
{"title": "First Slide", "content": ["Content A1", "Content A2"]},
{"title": "Second Slide", "content": ["Content B1", "Content B2"]}
])

# Call the function and check if file is created
output_filename = "test_slides.odp"
make_slides(slides_description_json, filename=output_filename)

import os
assert os.path.exists(output_filename)

# Clean up the created file
os.remove(output_filename)