Spaces:
Sleeping
Sleeping
Fix #29: improve JSON handling and keys
Browse files- app.py +31 -20
- helpers/pptx_helper.py +32 -24
app.py
CHANGED
|
@@ -7,7 +7,7 @@ import os
|
|
| 7 |
import pathlib
|
| 8 |
import random
|
| 9 |
import tempfile
|
| 10 |
-
from typing import List
|
| 11 |
|
| 12 |
import json5
|
| 13 |
import streamlit as st
|
|
@@ -240,7 +240,9 @@ def set_up_chat_ui():
|
|
| 240 |
progress_bar.progress(1.0, text='Done!')
|
| 241 |
|
| 242 |
st.chat_message('ai').code(response, language='json')
|
| 243 |
-
|
|
|
|
|
|
|
| 244 |
|
| 245 |
logger.info(
|
| 246 |
'#messages in history / 2: %d',
|
|
@@ -248,15 +250,38 @@ def set_up_chat_ui():
|
|
| 248 |
)
|
| 249 |
|
| 250 |
|
| 251 |
-
def generate_slide_deck(json_str: str) -> pathlib.Path:
|
| 252 |
"""
|
| 253 |
Create a slide deck and return the file path. In case there is any error creating the slide
|
| 254 |
deck, the path may be to an empty file.
|
| 255 |
|
| 256 |
:param json_str: The content in *valid* JSON format.
|
| 257 |
-
:return: The
|
| 258 |
"""
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
if DOWNLOAD_FILE_KEY in st.session_state:
|
| 261 |
path = pathlib.Path(st.session_state[DOWNLOAD_FILE_KEY])
|
| 262 |
else:
|
|
@@ -267,24 +292,10 @@ def generate_slide_deck(json_str: str) -> pathlib.Path:
|
|
| 267 |
if temp:
|
| 268 |
temp.close()
|
| 269 |
|
| 270 |
-
logger.debug('Creating PPTX file: %s...', st.session_state[DOWNLOAD_FILE_KEY])
|
| 271 |
-
|
| 272 |
try:
|
|
|
|
| 273 |
pptx_helper.generate_powerpoint_presentation(
|
| 274 |
-
|
| 275 |
-
slides_template=pptx_template,
|
| 276 |
-
output_file_path=path
|
| 277 |
-
)
|
| 278 |
-
except ValueError:
|
| 279 |
-
st.error(
|
| 280 |
-
'Encountered error while parsing JSON...will fix it and retry'
|
| 281 |
-
)
|
| 282 |
-
logger.error(
|
| 283 |
-
'Caught ValueError: trying again after repairing JSON...'
|
| 284 |
-
)
|
| 285 |
-
|
| 286 |
-
pptx_helper.generate_powerpoint_presentation(
|
| 287 |
-
text_helper.fix_malformed_json(json_str),
|
| 288 |
slides_template=pptx_template,
|
| 289 |
output_file_path=path
|
| 290 |
)
|
|
|
|
| 7 |
import pathlib
|
| 8 |
import random
|
| 9 |
import tempfile
|
| 10 |
+
from typing import List, Union
|
| 11 |
|
| 12 |
import json5
|
| 13 |
import streamlit as st
|
|
|
|
| 240 |
progress_bar.progress(1.0, text='Done!')
|
| 241 |
|
| 242 |
st.chat_message('ai').code(response, language='json')
|
| 243 |
+
|
| 244 |
+
if path:
|
| 245 |
+
_display_download_button(path)
|
| 246 |
|
| 247 |
logger.info(
|
| 248 |
'#messages in history / 2: %d',
|
|
|
|
| 250 |
)
|
| 251 |
|
| 252 |
|
| 253 |
+
def generate_slide_deck(json_str: str) -> Union[pathlib.Path, None]:
|
| 254 |
"""
|
| 255 |
Create a slide deck and return the file path. In case there is any error creating the slide
|
| 256 |
deck, the path may be to an empty file.
|
| 257 |
|
| 258 |
:param json_str: The content in *valid* JSON format.
|
| 259 |
+
:return: The path to the .pptx file or `None` in case of error.
|
| 260 |
"""
|
| 261 |
|
| 262 |
+
try:
|
| 263 |
+
parsed_data = json5.loads(json_str)
|
| 264 |
+
except ValueError:
|
| 265 |
+
st.error(
|
| 266 |
+
'Encountered error while parsing JSON...will fix it and retry'
|
| 267 |
+
)
|
| 268 |
+
logger.error(
|
| 269 |
+
'Caught ValueError: trying again after repairing JSON...'
|
| 270 |
+
)
|
| 271 |
+
try:
|
| 272 |
+
parsed_data = json5.loads(text_helper.fix_malformed_json(json_str))
|
| 273 |
+
except ValueError:
|
| 274 |
+
st.error(
|
| 275 |
+
'Encountered an error again while fixing JSON...'
|
| 276 |
+
'the slide deck cannot be created, unfortunately ☹'
|
| 277 |
+
'\nPlease try again later.'
|
| 278 |
+
)
|
| 279 |
+
logger.error(
|
| 280 |
+
'Caught ValueError: failed to repair JSON!'
|
| 281 |
+
)
|
| 282 |
+
|
| 283 |
+
return None
|
| 284 |
+
|
| 285 |
if DOWNLOAD_FILE_KEY in st.session_state:
|
| 286 |
path = pathlib.Path(st.session_state[DOWNLOAD_FILE_KEY])
|
| 287 |
else:
|
|
|
|
| 292 |
if temp:
|
| 293 |
temp.close()
|
| 294 |
|
|
|
|
|
|
|
| 295 |
try:
|
| 296 |
+
logger.debug('Creating PPTX file: %s...', st.session_state[DOWNLOAD_FILE_KEY])
|
| 297 |
pptx_helper.generate_powerpoint_presentation(
|
| 298 |
+
parsed_data,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
slides_template=pptx_template,
|
| 300 |
output_file_path=path
|
| 301 |
)
|
helpers/pptx_helper.py
CHANGED
|
@@ -82,21 +82,19 @@ def remove_slide_number_from_heading(header: str) -> str:
|
|
| 82 |
|
| 83 |
|
| 84 |
def generate_powerpoint_presentation(
|
| 85 |
-
|
| 86 |
slides_template: str,
|
| 87 |
output_file_path: pathlib.Path
|
| 88 |
) -> List:
|
| 89 |
"""
|
| 90 |
Create and save a PowerPoint presentation file containing the content in JSON format.
|
| 91 |
|
| 92 |
-
:param
|
| 93 |
:param slides_template: The PPTX template to use.
|
| 94 |
:param output_file_path: The path of the PPTX file to save as.
|
| 95 |
:return: A list of presentation title and slides headers.
|
| 96 |
"""
|
| 97 |
|
| 98 |
-
# The structured "JSON" might contain trailing commas, so using json5
|
| 99 |
-
parsed_data = json5.loads(structured_data)
|
| 100 |
presentation = pptx.Presentation(GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file'])
|
| 101 |
slide_width_inch, slide_height_inch = _get_slide_width_height_inches(presentation)
|
| 102 |
|
|
@@ -237,21 +235,22 @@ def _handle_default_display(
|
|
| 237 |
|
| 238 |
status = False
|
| 239 |
|
| 240 |
-
if
|
| 241 |
-
if random.random() <
|
| 242 |
-
|
| 243 |
-
|
| 244 |
-
|
| 245 |
-
|
| 246 |
-
|
| 247 |
-
|
| 248 |
-
|
| 249 |
-
|
| 250 |
-
|
| 251 |
-
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
|
|
|
|
| 255 |
|
| 256 |
if status:
|
| 257 |
return
|
|
@@ -301,7 +300,8 @@ def _handle_display_image__in_foreground(
|
|
| 301 |
slide_height_inch: float
|
| 302 |
) -> bool:
|
| 303 |
"""
|
| 304 |
-
Create a slide with text and image using a picture placeholder layout.
|
|
|
|
| 305 |
|
| 306 |
:param presentation: The presentation object.
|
| 307 |
:param slide_json: The content of the slide as JSON data.
|
|
@@ -386,7 +386,8 @@ def _handle_display_image__in_background(
|
|
| 386 |
) -> bool:
|
| 387 |
"""
|
| 388 |
Add a slide with text and an image in the background. It works just like
|
| 389 |
-
`_handle_default_display()` but with a background image added.
|
|
|
|
| 390 |
|
| 391 |
:param presentation: The presentation object.
|
| 392 |
:param slide_json: The content of the slide as JSON data.
|
|
@@ -566,6 +567,14 @@ def _handle_icons_ideas(
|
|
| 566 |
for run in paragraph.runs:
|
| 567 |
run.font.color.theme_color = pptx.enum.dml.MSO_THEME_COLOR.TEXT_2
|
| 568 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 569 |
return True
|
| 570 |
|
| 571 |
return False
|
|
@@ -928,7 +937,6 @@ if __name__ == '__main__':
|
|
| 928 |
"AI for predicting student performance and dropout rates"
|
| 929 |
],
|
| 930 |
"key_message": "AI is personalizing education and improving student outcomes",
|
| 931 |
-
"img_keywords": "education, personalized learning, intelligent tutoring, student performance"
|
| 932 |
},
|
| 933 |
{
|
| 934 |
"heading": "Step-by-Step: AI Development Process",
|
|
@@ -940,7 +948,7 @@ if __name__ == '__main__':
|
|
| 940 |
">> Deploy and monitor the AI system"
|
| 941 |
],
|
| 942 |
"key_message": "Developing AI involves a structured process from problem definition to deployment",
|
| 943 |
-
"img_keywords": "
|
| 944 |
},
|
| 945 |
{
|
| 946 |
"heading": "AI Icons: Key Aspects",
|
|
@@ -974,7 +982,7 @@ if __name__ == '__main__':
|
|
| 974 |
generate_powerpoint_presentation(
|
| 975 |
json5.loads(_JSON_DATA),
|
| 976 |
output_file_path=path,
|
| 977 |
-
slides_template='
|
| 978 |
)
|
| 979 |
print(f'File path: {path}')
|
| 980 |
|
|
|
|
| 82 |
|
| 83 |
|
| 84 |
def generate_powerpoint_presentation(
|
| 85 |
+
parsed_data: dict,
|
| 86 |
slides_template: str,
|
| 87 |
output_file_path: pathlib.Path
|
| 88 |
) -> List:
|
| 89 |
"""
|
| 90 |
Create and save a PowerPoint presentation file containing the content in JSON format.
|
| 91 |
|
| 92 |
+
:param parsed_data: The presentation content as parsed JSON data.
|
| 93 |
:param slides_template: The PPTX template to use.
|
| 94 |
:param output_file_path: The path of the PPTX file to save as.
|
| 95 |
:return: A list of presentation title and slides headers.
|
| 96 |
"""
|
| 97 |
|
|
|
|
|
|
|
| 98 |
presentation = pptx.Presentation(GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file'])
|
| 99 |
slide_width_inch, slide_height_inch = _get_slide_width_height_inches(presentation)
|
| 100 |
|
|
|
|
| 235 |
|
| 236 |
status = False
|
| 237 |
|
| 238 |
+
if 'img_keywords' in slide_json:
|
| 239 |
+
if random.random() < IMAGE_DISPLAY_PROBABILITY:
|
| 240 |
+
if random.random() < FOREGROUND_IMAGE_PROBABILITY:
|
| 241 |
+
status = _handle_display_image__in_foreground(
|
| 242 |
+
presentation,
|
| 243 |
+
slide_json,
|
| 244 |
+
slide_width_inch,
|
| 245 |
+
slide_height_inch
|
| 246 |
+
)
|
| 247 |
+
else:
|
| 248 |
+
status = _handle_display_image__in_background(
|
| 249 |
+
presentation,
|
| 250 |
+
slide_json,
|
| 251 |
+
slide_width_inch,
|
| 252 |
+
slide_height_inch
|
| 253 |
+
)
|
| 254 |
|
| 255 |
if status:
|
| 256 |
return
|
|
|
|
| 300 |
slide_height_inch: float
|
| 301 |
) -> bool:
|
| 302 |
"""
|
| 303 |
+
Create a slide with text and image using a picture placeholder layout. If not image keyword is
|
| 304 |
+
available, it will add only text to the slide.
|
| 305 |
|
| 306 |
:param presentation: The presentation object.
|
| 307 |
:param slide_json: The content of the slide as JSON data.
|
|
|
|
| 386 |
) -> bool:
|
| 387 |
"""
|
| 388 |
Add a slide with text and an image in the background. It works just like
|
| 389 |
+
`_handle_default_display()` but with a background image added. If not image keyword is
|
| 390 |
+
available, it will add only text to the slide.
|
| 391 |
|
| 392 |
:param presentation: The presentation object.
|
| 393 |
:param slide_json: The content of the slide as JSON data.
|
|
|
|
| 567 |
for run in paragraph.runs:
|
| 568 |
run.font.color.theme_color = pptx.enum.dml.MSO_THEME_COLOR.TEXT_2
|
| 569 |
|
| 570 |
+
_add_text_at_bottom(
|
| 571 |
+
slide=slide,
|
| 572 |
+
slide_width_inch=slide_width_inch,
|
| 573 |
+
slide_height_inch=slide_height_inch,
|
| 574 |
+
text='More icons available in the SlideDeck AI repository',
|
| 575 |
+
hyperlink='https://github.com/barun-saha/slide-deck-ai/tree/main/icons/png128'
|
| 576 |
+
)
|
| 577 |
+
|
| 578 |
return True
|
| 579 |
|
| 580 |
return False
|
|
|
|
| 937 |
"AI for predicting student performance and dropout rates"
|
| 938 |
],
|
| 939 |
"key_message": "AI is personalizing education and improving student outcomes",
|
|
|
|
| 940 |
},
|
| 941 |
{
|
| 942 |
"heading": "Step-by-Step: AI Development Process",
|
|
|
|
| 948 |
">> Deploy and monitor the AI system"
|
| 949 |
],
|
| 950 |
"key_message": "Developing AI involves a structured process from problem definition to deployment",
|
| 951 |
+
"img_keywords": ""
|
| 952 |
},
|
| 953 |
{
|
| 954 |
"heading": "AI Icons: Key Aspects",
|
|
|
|
| 982 |
generate_powerpoint_presentation(
|
| 983 |
json5.loads(_JSON_DATA),
|
| 984 |
output_file_path=path,
|
| 985 |
+
slides_template='Basic'
|
| 986 |
)
|
| 987 |
print(f'File path: {path}')
|
| 988 |
|