barunsaha commited on
Commit
4c0ac93
·
unverified ·
2 Parent(s): 79d9752 008172e

Merge pull request #15 from barun-saha/visual

Browse files
global_config.py CHANGED
@@ -25,8 +25,8 @@ class GlobalConfig:
25
  PRELOAD_DATA_FILE = 'examples/example_02.json'
26
  SLIDES_TEMPLATE_FILE = 'langchain_templates/template_combined.txt'
27
  # JSON_TEMPLATE_FILE = 'langchain_templates/text_to_json_template_02.txt'
28
- INITIAL_PROMPT_TEMPLATE = 'langchain_templates/chat_prompts/initial_template_v2_steps.txt'
29
- REFINEMENT_PROMPT_TEMPLATE = 'langchain_templates/chat_prompts/refinement_template_v2_steps.txt'
30
 
31
  PPTX_TEMPLATE_FILES = {
32
  'Basic': {
 
25
  PRELOAD_DATA_FILE = 'examples/example_02.json'
26
  SLIDES_TEMPLATE_FILE = 'langchain_templates/template_combined.txt'
27
  # JSON_TEMPLATE_FILE = 'langchain_templates/text_to_json_template_02.txt'
28
+ INITIAL_PROMPT_TEMPLATE = 'langchain_templates/chat_prompts/initial_template_v3_two_cols.txt'
29
+ REFINEMENT_PROMPT_TEMPLATE = 'langchain_templates/chat_prompts/refinement_template_v3_two_cols.txt'
30
 
31
  PPTX_TEMPLATE_FILES = {
32
  'Basic': {
helpers/pptx_helper.py CHANGED
@@ -79,10 +79,7 @@ def generate_powerpoint_presentation(
79
  GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file']
80
  )
81
  presentation = pptx.Presentation(GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file'])
82
-
83
- slide_width_inch = EMU_TO_INCH_SCALING_FACTOR * presentation.slide_width
84
- slide_height_inch = EMU_TO_INCH_SCALING_FACTOR * presentation.slide_height
85
- logger.debug('Slide width: %f, height: %f', slide_width_inch, slide_height_inch)
86
 
87
  # The title slide
88
  title_slide_layout = presentation.slide_layouts[0]
@@ -97,26 +94,31 @@ def generate_powerpoint_presentation(
97
  subtitle.text = 'by Myself and SlideDeck AI :)'
98
  all_headers = [title.text, ]
99
 
100
- # Add contents in a loop
101
  for a_slide in parsed_data['slides']:
102
- bullet_slide_layout = presentation.slide_layouts[1]
103
- slide = presentation.slides.add_slide(bullet_slide_layout)
104
-
105
- if not _handle_step_by_step_process(
106
- slide=slide,
107
- slide_json=a_slide,
108
- slide_width_inch=slide_width_inch,
109
- slide_height_inch=slide_height_inch,
110
- ):
111
- _handle_default_display(slide, a_slide)
112
-
113
- _handle_key_message(
114
- slide=slide,
115
  slide_json=a_slide,
116
  slide_width_inch=slide_width_inch,
117
- slide_height_inch=slide_height_inch,
118
  )
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  # The thank-you slide
121
  last_slide_layout = presentation.slide_layouts[0]
122
  slide = presentation.slides.add_slide(last_slide_layout)
@@ -150,16 +152,23 @@ def get_flat_list_of_contents(items: list, level: int) -> List[Tuple]:
150
 
151
 
152
  def _handle_default_display(
153
- slide: pptx.slide.Slide,
154
  slide_json: dict,
 
 
155
  ):
156
  """
157
  Display a list of text in a slide.
158
 
159
- :param slide: The slide to be processed.
160
  :param slide_json: The content of the slide as JSON data.
 
 
161
  """
162
 
 
 
 
163
  shapes = slide.shapes
164
  title_shape = shapes.title
165
  body_shape = shapes.placeholders[1]
@@ -172,14 +181,99 @@ def _handle_default_display(
172
 
173
  flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
174
 
175
- for an_item in flat_items_list:
176
- paragraph = text_frame.add_paragraph()
177
- paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
178
- paragraph.level = an_item[1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
 
181
  def _handle_step_by_step_process(
182
- slide: pptx.slide.Slide,
183
  slide_json: dict,
184
  slide_width_inch: float,
185
  slide_height_inch: float
@@ -187,11 +281,11 @@ def _handle_step_by_step_process(
187
  """
188
  Add shapes to display a step-by-step process in the slide, if available.
189
 
190
- :param slide: The slide to be processed.
191
  :param slide_json: The content of the slide as JSON data.
192
  :param slide_width_inch: The width of the slide in inches.
193
  :param slide_height_inch: The height of the slide in inches.
194
- :return True is this slide has a step-by-step process depiction; False otherwise.
195
  """
196
 
197
  if 'bullet_points' in slide_json and slide_json['bullet_points']:
@@ -228,6 +322,8 @@ def _handle_step_by_step_process(
228
  ):
229
  return False
230
 
 
 
231
  shapes = slide.shapes
232
  shapes.title.text = remove_slide_number_from_heading(slide_json['heading'])
233
 
@@ -277,7 +373,7 @@ def _handle_step_by_step_process(
277
 
278
 
279
  def _handle_key_message(
280
- slide: pptx.slide.Slide,
281
  slide_json: dict,
282
  slide_width_inch: float,
283
  slide_height_inch: float
@@ -285,7 +381,7 @@ def _handle_key_message(
285
  """
286
  Add a shape to display the key message in the slide, if available.
287
 
288
- :param slide: The slide to be processed.
289
  :param slide_json: The content of the slide as JSON data.
290
  :param slide_width_inch: The width of the slide in inches.
291
  :param slide_height_inch: The height of the slide in inches.
@@ -296,7 +392,7 @@ def _handle_key_message(
296
  width = pptx.util.Inches(slide_width_inch / 2.3)
297
  top = pptx.util.Inches(slide_height_inch - height.inches - 0.1)
298
  left = pptx.util.Inches((slide_width_inch - width.inches) / 2)
299
- shape = slide.shapes.add_shape(
300
  MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE,
301
  left=left,
302
  top=top,
@@ -306,6 +402,21 @@ def _handle_key_message(
306
  shape.text = slide_json['key_message']
307
 
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  if __name__ == '__main__':
310
  _JSON_DATA = '''
311
  {
@@ -363,6 +474,28 @@ if __name__ == '__main__':
363
  ],
364
  "key_message": ""
365
  },
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
  {
367
  "heading": "Pros of AI",
368
  "bullet_points": [
 
79
  GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file']
80
  )
81
  presentation = pptx.Presentation(GlobalConfig.PPTX_TEMPLATE_FILES[slides_template]['file'])
82
+ slide_width_inch, slide_height_inch = _get_slide_width_height_inches(presentation)
 
 
 
83
 
84
  # The title slide
85
  title_slide_layout = presentation.slide_layouts[0]
 
94
  subtitle.text = 'by Myself and SlideDeck AI :)'
95
  all_headers = [title.text, ]
96
 
97
+ # Add content in a loop
98
  for a_slide in parsed_data['slides']:
99
+ is_processing_done = _handle_double_col_layout(
100
+ presentation=presentation,
 
 
 
 
 
 
 
 
 
 
 
101
  slide_json=a_slide,
102
  slide_width_inch=slide_width_inch,
103
+ slide_height_inch=slide_height_inch
104
  )
105
 
106
+ if not is_processing_done:
107
+ is_processing_done = _handle_step_by_step_process(
108
+ presentation=presentation,
109
+ slide_json=a_slide,
110
+ slide_width_inch=slide_width_inch,
111
+ slide_height_inch=slide_height_inch
112
+ )
113
+
114
+ if not is_processing_done:
115
+ _handle_default_display(
116
+ presentation=presentation,
117
+ slide_json=a_slide,
118
+ slide_width_inch=slide_width_inch,
119
+ slide_height_inch=slide_height_inch
120
+ )
121
+
122
  # The thank-you slide
123
  last_slide_layout = presentation.slide_layouts[0]
124
  slide = presentation.slides.add_slide(last_slide_layout)
 
152
 
153
 
154
  def _handle_default_display(
155
+ presentation: pptx.Presentation,
156
  slide_json: dict,
157
+ slide_width_inch: float,
158
+ slide_height_inch: float
159
  ):
160
  """
161
  Display a list of text in a slide.
162
 
163
+ :param presentation: The presentation object.
164
  :param slide_json: The content of the slide as JSON data.
165
+ :param slide_width_inch: The width of the slide in inches.
166
+ :param slide_height_inch: The height of the slide in inches.
167
  """
168
 
169
+ bullet_slide_layout = presentation.slide_layouts[1]
170
+ slide = presentation.slides.add_slide(bullet_slide_layout)
171
+
172
  shapes = slide.shapes
173
  title_shape = shapes.title
174
  body_shape = shapes.placeholders[1]
 
181
 
182
  flat_items_list = get_flat_list_of_contents(slide_json['bullet_points'], level=0)
183
 
184
+ for idx, an_item in enumerate(flat_items_list):
185
+ if idx == 0:
186
+ text_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
187
+ else:
188
+ paragraph = text_frame.add_paragraph()
189
+ paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
190
+ paragraph.level = an_item[1]
191
+
192
+ _handle_key_message(
193
+ the_slide=slide,
194
+ slide_json=slide_json,
195
+ slide_height_inch=slide_height_inch,
196
+ slide_width_inch=slide_width_inch
197
+ )
198
+
199
+
200
+ def _handle_double_col_layout(
201
+ presentation: pptx.Presentation(),
202
+ slide_json: dict,
203
+ slide_width_inch: float,
204
+ slide_height_inch: float
205
+ ) -> bool:
206
+ """
207
+ Add a slide with a double column layout for comparison.
208
+
209
+ :param presentation: The presentation object.
210
+ :param slide_json: The content of the slide as JSON data.
211
+ :param slide_width_inch: The width of the slide in inches.
212
+ :param slide_height_inch: The height of the slide in inches.
213
+ :return: True if double col layout has been added; False otherwise.
214
+ """
215
+
216
+ if 'bullet_points' in slide_json and slide_json['bullet_points']:
217
+ double_col_content = slide_json['bullet_points']
218
+
219
+ if double_col_content and (
220
+ len(double_col_content) == 2
221
+ ) and isinstance(double_col_content[0], dict) and isinstance(double_col_content[1], dict):
222
+ slide = presentation.slide_layouts[4]
223
+ slide = presentation.slides.add_slide(slide)
224
+
225
+ shapes = slide.shapes
226
+ title_placeholder = shapes.title
227
+ title_placeholder.text = remove_slide_number_from_heading(slide_json['heading'])
228
+
229
+ left_heading, right_heading = shapes.placeholders[1], shapes.placeholders[3]
230
+ left_col, right_col = shapes.placeholders[2], shapes.placeholders[4]
231
+ left_col_frame, right_col_frame = left_col.text_frame, right_col.text_frame
232
+
233
+ if 'heading' in double_col_content[0]:
234
+ left_heading.text = double_col_content[0]['heading']
235
+ if 'bullet_points' in double_col_content[0]:
236
+ flat_items_list = get_flat_list_of_contents(
237
+ double_col_content[0]['bullet_points'], level=0
238
+ )
239
+
240
+ for idx, an_item in enumerate(flat_items_list):
241
+ if idx == 0:
242
+ left_col_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
243
+ else:
244
+ paragraph = left_col_frame.add_paragraph()
245
+ paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
246
+ paragraph.level = an_item[1]
247
+
248
+ if 'heading' in double_col_content[1]:
249
+ right_heading.text = double_col_content[1]['heading']
250
+ if 'bullet_points' in double_col_content[1]:
251
+ flat_items_list = get_flat_list_of_contents(
252
+ double_col_content[1]['bullet_points'], level=0
253
+ )
254
+
255
+ for idx, an_item in enumerate(flat_items_list):
256
+ if idx == 0:
257
+ right_col_frame.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
258
+ else:
259
+ paragraph = right_col_frame.add_paragraph()
260
+ paragraph.text = an_item[0].removeprefix(STEP_BY_STEP_PROCESS_MARKER)
261
+ paragraph.level = an_item[1]
262
+
263
+ _handle_key_message(
264
+ the_slide=slide,
265
+ slide_json=slide_json,
266
+ slide_height_inch=slide_height_inch,
267
+ slide_width_inch=slide_width_inch
268
+ )
269
+
270
+ return True
271
+
272
+ return False
273
 
274
 
275
  def _handle_step_by_step_process(
276
+ presentation: pptx.Presentation,
277
  slide_json: dict,
278
  slide_width_inch: float,
279
  slide_height_inch: float
 
281
  """
282
  Add shapes to display a step-by-step process in the slide, if available.
283
 
284
+ :param presentation: The presentation object.
285
  :param slide_json: The content of the slide as JSON data.
286
  :param slide_width_inch: The width of the slide in inches.
287
  :param slide_height_inch: The height of the slide in inches.
288
+ :return True if this slide has a step-by-step process depiction added; False otherwise.
289
  """
290
 
291
  if 'bullet_points' in slide_json and slide_json['bullet_points']:
 
322
  ):
323
  return False
324
 
325
+ bullet_slide_layout = presentation.slide_layouts[1]
326
+ slide = presentation.slides.add_slide(bullet_slide_layout)
327
  shapes = slide.shapes
328
  shapes.title.text = remove_slide_number_from_heading(slide_json['heading'])
329
 
 
373
 
374
 
375
  def _handle_key_message(
376
+ the_slide: pptx.slide.Slide,
377
  slide_json: dict,
378
  slide_width_inch: float,
379
  slide_height_inch: float
 
381
  """
382
  Add a shape to display the key message in the slide, if available.
383
 
384
+ :param the_slide: The slide to be processed.
385
  :param slide_json: The content of the slide as JSON data.
386
  :param slide_width_inch: The width of the slide in inches.
387
  :param slide_height_inch: The height of the slide in inches.
 
392
  width = pptx.util.Inches(slide_width_inch / 2.3)
393
  top = pptx.util.Inches(slide_height_inch - height.inches - 0.1)
394
  left = pptx.util.Inches((slide_width_inch - width.inches) / 2)
395
+ shape = the_slide.shapes.add_shape(
396
  MSO_AUTO_SHAPE_TYPE.ROUNDED_RECTANGLE,
397
  left=left,
398
  top=top,
 
402
  shape.text = slide_json['key_message']
403
 
404
 
405
+ def _get_slide_width_height_inches(presentation: pptx.Presentation) -> Tuple[float, float]:
406
+ """
407
+ Get the dimensions of a slide in inches.
408
+
409
+ :param presentation: The presentation object.
410
+ :return: The width and the height.
411
+ """
412
+
413
+ slide_width_inch = EMU_TO_INCH_SCALING_FACTOR * presentation.slide_width
414
+ slide_height_inch = EMU_TO_INCH_SCALING_FACTOR * presentation.slide_height
415
+ # logger.debug('Slide width: %f, height: %f', slide_width_inch, slide_height_inch)
416
+
417
+ return slide_width_inch, slide_height_inch
418
+
419
+
420
  if __name__ == '__main__':
421
  _JSON_DATA = '''
422
  {
 
474
  ],
475
  "key_message": ""
476
  },
477
+ {
478
+ "heading": "Pros and Cons: Deep Learning vs. Classical Machine Learning",
479
+ "bullet_points": [
480
+ {
481
+ "heading": "Classical Machine Learning",
482
+ "bullet_points": [
483
+ "Interpretability: Easy to understand the model",
484
+ "Faster Training: Quicker to train models",
485
+ "Scalability: Can handle large datasets"
486
+ ]
487
+ },
488
+ {
489
+ "heading": "Deep Learning",
490
+ "bullet_points": [
491
+ "Handling Complex Data: Can learn from raw data",
492
+ "Feature Extraction: Automatically learns features",
493
+ "Improved Accuracy: Achieves higher accuracy"
494
+ ]
495
+ }
496
+ ],
497
+ "key_message": ""
498
+ },
499
  {
500
  "heading": "Pros of AI",
501
  "bullet_points": [
langchain_templates/chat_prompts/initial_template_v3_two_cols.txt ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful, intelligent chatbot. Create the slides for a presentation on the given topic.
2
+ Include main headings for each slide, detailed bullet points for each slide.
3
+ Add relevant content to each slide.
4
+ The content of each slide should be verbose, descriptive, and very detailed.
5
+ If relevant, add one or two examples to illustrate the concept.
6
+ Unless explicitly specified with the topic, create about 10 slides.
7
+
8
+
9
+ ### Topic:
10
+ {question}
11
+
12
+
13
+ The output must be only a valid and syntactically correct JSON adhering to the following schema:
14
+ {{
15
+ "title": "Presentation Title",
16
+ "slides": [
17
+ {{
18
+ "heading": "Heading for the First Slide",
19
+ "bullet_points": [
20
+ "First bullet point",
21
+ [
22
+ "Sub-bullet point 1",
23
+ "Sub-bullet point 2"
24
+ ],
25
+ "Second bullet point"
26
+ ],
27
+ "key_message": ""
28
+ }},
29
+ {{
30
+ "heading": "Heading for the Second Slide",
31
+ "bullet_points": [
32
+ "First bullet point",
33
+ "Second bullet item",
34
+ "Third bullet point"
35
+ ],
36
+ "key_message": "The key message conveyed in this slide"
37
+ }},
38
+ {{
39
+ "heading": "A slide that describes a step-by-step/sequential process",
40
+ "bullet_points": [
41
+ ">> The first step of the process (begins with special marker >>)",
42
+ ">> A second step (begins with >>)",
43
+ ">> Third step",
44
+ ],
45
+ "key_message": ""
46
+ }},
47
+ {{
48
+ "heading": "A slide with a double column layout (useful for side-by-side comparison/contrasting of related concepts, e.g., pros & cons, advantages & risks, old approach vs. modern approach, and so on)",
49
+ "bullet_points": [
50
+ {{
51
+ "heading": "Heading of the left column",
52
+ "bullet_points": [
53
+ "First bullet point",
54
+ "Second bullet item",
55
+ "Third bullet point"
56
+ ]
57
+ }},
58
+ {{
59
+ "heading": "Heading of the right column",
60
+ "bullet_points": [
61
+ "First bullet point",
62
+ "Second bullet item",
63
+ "Third bullet point"
64
+ ]
65
+ }}
66
+ ],
67
+ "key_message": ""
68
+ }}
69
+ ]
70
+ }}
71
+
72
+
73
+ ### Some more hints on the slide content and JSON output format:
74
+ - For two or three important slides, generate the key message that those slides convey and assign
75
+ them to the `key_message` elements of JSON output.
76
+ - Identify if a slide describes a step-by-step/sequential process, then begin the bullet points
77
+ with a special marker >>. Limit this to max two or three slides.
78
+ - Add at least one slide with double column layout by generating appropriate content based on
79
+ the description in the above JSON schema.
80
+
81
+
82
+ ### Output:
83
+ ```json
langchain_templates/chat_prompts/refinement_template_v3_two_cols.txt ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ You are a helpful, intelligent chatbot. You follow instructions to refine an existing slide deck.
2
+ A list of user instructions is provided below in sequential order -- from the oldest to the latest.
3
+ The previously generated content of the slide deck in JSON format is also provided.
4
+ Follow the instructions to revise the content of the previously generated slides of the presentation on the given topic.
5
+ Include main headings for each slide, detailed bullet points for each slide.
6
+ Add relevant content to each slide.
7
+ The content of the slides should be descriptive, verbose, and detailed.
8
+ If relevant, add one or two examples to illustrate the concept.
9
+ Unless explicitly specified with the topic, create about 10 slides.
10
+ You also fix any syntax error that may be present in the JSON-formatted content.
11
+
12
+ A slide that describes a step-by-step/sequential process begins the bullet points
13
+ with a special marker >>
14
+
15
+
16
+ ### List of instructions:
17
+ {instructions}
18
+
19
+
20
+ ### Previously generated slide deck content as JSON:
21
+ {previous_content}
22
+
23
+
24
+ The output must be only a valid and syntactically correct JSON adhering to the following schema:
25
+ {{
26
+ "title": "Presentation Title",
27
+ "slides": [
28
+ {{
29
+ "heading": "Heading for the First Slide",
30
+ "bullet_points": [
31
+ "First bullet point",
32
+ [
33
+ "Sub-bullet point 1",
34
+ "Sub-bullet point 2"
35
+ ],
36
+ "Second bullet point"
37
+ ],
38
+ "key_message": ""
39
+ }},
40
+ {{
41
+ "heading": "Heading for the Second Slide",
42
+ "bullet_points": [
43
+ "First bullet point",
44
+ "Second bullet item",
45
+ "Third bullet point"
46
+ ],
47
+ "key_message": "The key message conveyed in this slide"
48
+ }},
49
+ {{
50
+ "heading": "A slide that describes a step-by-step/sequential process",
51
+ "bullet_points": [
52
+ ">> The first step of the process (begins with special marker >>)",
53
+ ">> A second step (begins with >>)",
54
+ ">> Third step",
55
+ ],
56
+ "key_message": ""
57
+ }},
58
+ {{
59
+ "heading": "A slide with a double column layout (useful for side-by-side comparison/contrasting of related concepts, e.g., pros & cons, advantages & risks, old approach vs. modern approach, and so on)",
60
+ "bullet_points": [
61
+ {{
62
+ "heading": "Heading of the left column",
63
+ "bullet_points": [
64
+ "First bullet point",
65
+ "Second bullet item",
66
+ "Third bullet point"
67
+ ]
68
+ }},
69
+ {{
70
+ "heading": "Heading of the right column",
71
+ "bullet_points": [
72
+ "First bullet point",
73
+ "Second bullet item",
74
+ "Third bullet point"
75
+ ]
76
+ }}
77
+ ],
78
+ "key_message": ""
79
+ }}
80
+ ]
81
+ }}
82
+
83
+
84
+ ### Some more hints on the slide content and JSON output format:
85
+ - For two or three important slides, generate the key message that those slides convey and assign
86
+ them to the `key_message` elements of JSON output.
87
+ - Identify if a slide describes a step-by-step/sequential process, then begin the bullet points
88
+ with a special marker >>. Limit this to max two or three slides.
89
+ - Add at least one slide with double column layout by generating appropriate content based on
90
+ the description in the above JSON schema.
91
+
92
+
93
+ ### Output:
94
+ ```json