chatterer 0.1.26__py3-none-any.whl → 0.1.28__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. chatterer/__init__.py +87 -87
  2. chatterer/common_types/__init__.py +21 -21
  3. chatterer/common_types/io.py +19 -19
  4. chatterer/constants.py +5 -0
  5. chatterer/examples/__main__.py +75 -75
  6. chatterer/examples/any2md.py +83 -85
  7. chatterer/examples/pdf2md.py +231 -338
  8. chatterer/examples/pdf2txt.py +52 -54
  9. chatterer/examples/ppt.py +487 -486
  10. chatterer/examples/pw.py +141 -143
  11. chatterer/examples/snippet.py +54 -56
  12. chatterer/examples/transcribe.py +192 -192
  13. chatterer/examples/upstage.py +87 -89
  14. chatterer/examples/web2md.py +80 -80
  15. chatterer/interactive.py +422 -354
  16. chatterer/language_model.py +530 -536
  17. chatterer/messages.py +21 -21
  18. chatterer/tools/__init__.py +46 -46
  19. chatterer/tools/caption_markdown_images.py +388 -384
  20. chatterer/tools/citation_chunking/__init__.py +3 -3
  21. chatterer/tools/citation_chunking/chunks.py +51 -53
  22. chatterer/tools/citation_chunking/citation_chunker.py +117 -118
  23. chatterer/tools/citation_chunking/citations.py +284 -285
  24. chatterer/tools/citation_chunking/prompt.py +157 -157
  25. chatterer/tools/citation_chunking/reference.py +26 -26
  26. chatterer/tools/citation_chunking/utils.py +138 -138
  27. chatterer/tools/convert_pdf_to_markdown.py +634 -645
  28. chatterer/tools/convert_to_text.py +446 -446
  29. chatterer/tools/upstage_document_parser.py +704 -705
  30. chatterer/tools/webpage_to_markdown.py +739 -739
  31. chatterer/tools/youtube.py +146 -147
  32. chatterer/utils/__init__.py +15 -15
  33. chatterer/utils/base64_image.py +349 -350
  34. chatterer/utils/bytesio.py +59 -59
  35. chatterer/utils/code_agent.py +237 -237
  36. chatterer/utils/imghdr.py +145 -145
  37. {chatterer-0.1.26.dist-info → chatterer-0.1.28.dist-info}/METADATA +377 -390
  38. chatterer-0.1.28.dist-info/RECORD +43 -0
  39. chatterer-0.1.26.dist-info/RECORD +0 -42
  40. {chatterer-0.1.26.dist-info → chatterer-0.1.28.dist-info}/WHEEL +0 -0
  41. {chatterer-0.1.26.dist-info → chatterer-0.1.28.dist-info}/entry_points.txt +0 -0
  42. {chatterer-0.1.26.dist-info → chatterer-0.1.28.dist-info}/top_level.txt +0 -0
chatterer/examples/ppt.py CHANGED
@@ -1,486 +1,487 @@
1
- import re
2
- import sys
3
- from pathlib import Path
4
- from typing import NotRequired, TypedDict
5
-
6
- from spargear import RunnableArguments
7
-
8
- from chatterer import BaseMessage, Chatterer, HumanMessage, SystemMessage
9
-
10
- # --- Default Prompts ---
11
-
12
- DEFAULT_ROLE_PROMPT = """\
13
- # Prompt Content
14
-
15
- ## Role Prompt: AI for Generating Presentation Slides and Scripts
16
-
17
- You are a professional AI assistant that generates presentation slides and corresponding speech scripts based on user-provided content. Your primary task is to create a visually appealing and highly informative single presentation slide using HTML/CSS, along with a natural and well-structured speech script to accompany the slide. You must pay close attention to avoid layout breakage due to unnecessary whitespace or line breaks in text content or code blocks.
18
-
19
- [Core Features]
20
-
21
- 1. **Slide Generation (HTML/CSS):**
22
- * Analyze the user's input, extract key information, and structure it logically.
23
- * **Text Normalization:** Before inserting text into HTML elements, remove unintended line breaks and excessive spacing caused by OCR or formatting errors. Ensure that sentences and list items flow naturally. (e.g., "Easy gram\\nmar:" → "Easy grammar:")
24
- * Design the slide based on a 16:9 aspect ratio (1280x720 pixels). Use the `.slide` class to explicitly define this size.
25
- * Use Tailwind CSS utilities (via CDN: `<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">`)—such as Grid, Flexbox, Padding, and Margin—to structure the layout. Prevent layout issues caused by long text lines or unwanted white space.
26
- * Enhance visuals with Font Awesome icons. Include the CDN in the `<head>` section (`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">`) and choose relevant icons appropriately.
27
- * Use Korean Google Fonts: `<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">`
28
- * **Code Block Handling:**
29
- * Use `<pre><code>` tags for code samples. (e.g., `<div class="code-block bg-gray-800 text-white p-4 rounded mt-2"><pre><code>...code...</code></pre></div>`)
30
- * Ensure that code lines inside `<code>` tags start without leading spaces to avoid rendering issues caused by HTML indentation.
31
- * Optionally use `<span>` tags and classes like `comment`, `keyword`, or `string` for syntax highlighting.
32
- * Carefully choose color, spacing, and typography to create a clean, professional layout.
33
- * The generated HTML code must be fully self-contained, including all style information (Tailwind classes and optionally custom CSS via `<style>`) and CDN links. It should be enclosed in ```html ... ``` markdown blocks.
34
-
35
- 2. **Speech Script Generation:**
36
- * Write a clear and effective speech script to accompany the generated slide.
37
- * Use a professional yet natural tone to ensure easy understanding for the audience.
38
- * Explain each component of the slide (title, main points, code samples, etc.) in order, and include transitional or explanatory remarks where appropriate.
39
- * Keep the speech concise but informative, covering all key points.
40
- * Present the script clearly, outside of the HTML block.
41
-
42
- [Workflow]
43
-
44
- 1. Receive the user's input (topic or content for the presentation slide).
45
- 2. Identify the key message, normalize the text, and structure it to fit into a single slide.
46
- 3. Design and generate a 1280x720 slide using Tailwind CSS and Font Awesome. Pay special attention to code block formatting. Enclose the *complete* HTML for the slide within ```html ... ```.
47
- 4. Write a natural speech script based on the slide's content. Place the script *after* the HTML block.
48
- 5. Clearly separate the HTML code (within ```html ... ```) and the speech script.
49
-
50
- [Guidelines]
51
-
52
- * Always aim to generate a single, self-contained slide.
53
- * Maintain high quality and structure similar to provided examples, but optimize the layout and content according to the input.
54
- * Pay close attention to whitespace and line break handling in both source HTML and rendered output, especially for plain text and code blocks.
55
- * Always use CDN links for external libraries (Tailwind, Font Awesome, Google Fonts).
56
- * Write the code with readability and reusability in mind.
57
- * The speech script must be tightly aligned with the slide content.
58
- * **Output Format:** First provide the complete HTML code block: ```html <!DOCTYPE html>...</html> ```. Immediately following the HTML block, provide the speech script as plain text.
59
-
60
- Incorrect Code Block HTML Example (To Avoid):
61
-
62
- ```html
63
- <!-- This renders unintended indentation due to <pre> preserving HTML source indentation -->
64
- <div class="code-block">
65
- <pre><code>
66
- score = 85
67
- if score >= 60:
68
- print("Passed!")
69
- else:
70
- print("Failed.")
71
- </code></pre>
72
- </div>
73
- ````
74
-
75
- Correct Code Block HTML Example (Recommended):
76
-
77
- ```html
78
- <div class="code-block bg-gray-800 text-white p-4 rounded">
79
- <pre><code>score = 85
80
- if score >= 60:
81
- print("Passed!") # Keep indentation within code block only
82
- else:
83
- print("Failed.")</code></pre>
84
- </div>
85
- ```"""
86
-
87
- DEFAULT_JOB_PROMPT = """\
88
- Objective: Analyze the content of the provided research material and create a detailed presentation slide plan in Markdown format. Save this plan to a file named 'plan.md'.
89
-
90
- Detailed Guidelines:
91
-
92
- 1. **Content Analysis:** Thoroughly analyze the provided research content to identify the core topic, main arguments, supporting data, conclusions, and other critical points.
93
- 2. **Slide Structure:** Based on the analysis, organize the overall slide structure with a logical flow (e.g., Introduction – Main Body – Conclusion, or Problem Solution). Aim for a reasonable number of slides to cover the material effectively without being overwhelming.
94
- 3. **Slide-by-Slide Planning:** For each slide, define the content in detail. Each slide plan must include the following elements:
95
- * **Slide Number:** Use the format `# Slide N`.
96
- * **Topic:** Clearly state the central theme of the slide. (`Topic: ...`)
97
- * **Summary:** Provide a concise summary of the key message, main content, relevant data, and possible visual ideas (like charts, key points, code snippets if applicable) to be included in the slide. (`Summary: ...`)
98
- 4. **Output:** Generate *only* the Markdown content for the slide plan, following the format example below. Do not include any other explanatory text before or after the plan.
99
-
100
- Output Format Example:
101
-
102
- ```markdown
103
- # Slide 1
104
-
105
- Topic: Background and Rationale
106
- Summary: Explain why the [research topic] is important and what current issues it addresses. Briefly reference relevant statistics or previous studies to highlight the need for this research. Consider using a compelling opening statistic or image.
107
-
108
- # Slide 2
109
-
110
- Topic: Research Objectives and Questions
111
- Summary: Clearly state the specific objectives of the research (e.g., To investigate X, To develop Y). Present 1–2 concise research questions that the study aims to answer. Use bullet points for clarity.
112
-
113
- # Slide 3
114
-
115
- Topic: Methodology
116
- Summary: Describe the research methods used (e.g., surveys, literature review, experiments, data analysis techniques). Summarize the data collection process and key parameters. Maybe include a simple flowchart icon.
117
-
118
- ... (and so on for all necessary slides) ...
119
-
120
- # Slide N
121
-
122
- Topic: Conclusion and Recommendations
123
- Summary: Summarize the key research findings and present the main conclusions clearly. Mention any significant limitations. Suggest directions for future research or practical recommendations based on the findings. End with a clear take-away message.
124
- ```
125
-
126
- --- START OF RESEARCH MATERIAL ---
127
- {research_content}
128
- --- END OF RESEARCH MATERIAL ---
129
-
130
- Now, generate the slide plan based *only* on the provided research material.
131
- """
132
-
133
- DEFAULT_ORGANIZATION_PROMPT = """\
134
- Objective: Create a final `presentation.html` file using impress.js (loaded via CDN) to structure the provided individual HTML slide contents into a cohesive presentation.
135
-
136
- Guidelines:
137
-
138
- 1. **Structure:** Use the standard impress.js HTML structure.
139
- * Include the impress.js library and its default CSS via CDN in the `<head>`.
140
- * Each slide's HTML content should be placed inside a `<div class="step">` element within the `<div id="impress">` container.
141
- * Assign appropriate `data-x`, `data-y`, `data-scale`, `data-rotate`, etc., attributes to the `step` divs to create a logical flow and visual transitions between slides. A simple linear flow (increasing `data-x` for each step) is acceptable, but feel free to add minor variations if it enhances the presentation flow.
142
- 2. **Content Integration:** Embed the *full* HTML content provided for each slide within its corresponding `<div class="step">`. Ensure the slide content (including its own `<head>` elements like Tailwind CSS links if they were generated per-slide) is correctly placed *inside* the `step` div. It might be better to consolidate CSS/Font links into the main HTML `<head>`. Let's aim to consolidate the CSS/Font links in the main `<head>` and put only the `<body>` content of each slide inside the `<div class="step">`.
143
- 3. **impress.js Initialization:** Include the `impress().init();` script at the end of the `<body>`.
144
- 4. **Output:** Generate *only* the complete HTML code for the `presentation.html` file. Do not include any other explanatory text.
145
-
146
- --- START OF SLIDE HTML CONTENTS ---
147
-
148
- {all_slides_html}
149
-
150
- --- END OF SLIDE HTML CONTENTS ---
151
-
152
- Now, generate the final `presentation.html` file using impress.js and the provided slide contents.
153
- """
154
-
155
- # --- Argument Parsing ---
156
-
157
-
158
- class Arguments(RunnableArguments[None]):
159
- """
160
- Arguments for the presentation generation process.
161
- """
162
-
163
- # Input file paths
164
- research_file: str = "research.md"
165
- """Path to the input research file"""
166
- plan_file: str = "plan.md"
167
- """Path to the slide plan file"""
168
- output_file: str = "presentation.html"
169
- """Path to the output presentation file"""
170
- slides_dir: str = "slides"
171
- """Directory to save individual slide files"""
172
-
173
- # Prompt templates
174
- role_prompt: str = DEFAULT_ROLE_PROMPT
175
- """Role prompt for the AI assistant"""
176
- job_prompt: str = DEFAULT_JOB_PROMPT
177
- """Job prompt for content analysis and slide planning"""
178
- organization_prompt: str = DEFAULT_ORGANIZATION_PROMPT
179
- """Prompt for organizing slides into a presentation script"""
180
-
181
- # LLM Settings
182
- provider: str = "openai:gpt-4.1" # Example: "openai:gpt-4o", "anthropic:claude-3-haiku-20240307", "google:gemini-1.5-flash"
183
- """Name of the language model to use (provider:model_name)"""
184
-
185
- # Other settings
186
- verbose: bool = True
187
- """Flag for verbose output during processing"""
188
-
189
- def run(self) -> None:
190
- # Create a dummy input file if it doesn't exist for testing
191
- if not Path(self.research_file).exists():
192
- print(f"Creating dummy input file: {self.research_file}")
193
- Path(self.research_file).write_text(
194
- """\
195
- # Research Paper: The Future of AI Assistants
196
-
197
- ## Introduction
198
- The field of Artificial Intelligence (AI) has seen exponential growth. AI assistants are becoming integrated into daily life. This research explores future trends.
199
-
200
- ## Current State
201
- Current assistants (Siri, Alexa, Google Assistant) primarily handle simple commands, Q&A, and basic task automation. They rely heavily on predefined scripts and cloud connectivity. NLP has improved significantly, but true contextual understanding remains a challenge.
202
-
203
- ## Key Trends
204
- 1. **Proactive Assistance:** Assistants will anticipate user needs.
205
- 2. **Hyper-Personalization:** Tailoring responses and actions based on deep user understanding.
206
- 3. **Multimodal Interaction:** Seamlessly integrating voice, text, vision, and gestures.
207
- 4. **On-Device Processing:** Enhancing privacy and speed by reducing cloud dependency. Example: `model.run_locally()`
208
- 5. **Emotional Intelligence:** Recognizing and responding appropriately to user emotions.
209
-
210
- ## Challenges
211
- * Data Privacy and Security
212
- * Algorithmic Bias
213
- * Computational Cost
214
- * Maintaining User Trust
215
-
216
- ## Conclusion
217
- The future of AI assistants points towards highly personalized, proactive, and emotionally intelligent companions. Overcoming challenges related to privacy and bias is crucial for widespread adoption. Python code example:
218
- ```python
219
- def greet(user):
220
- # Simple greeting
221
- print(f"Hello, {user}! How can I assist today?")
222
-
223
- greet("Developer")
224
- ```
225
- """,
226
- encoding="utf-8",
227
- )
228
-
229
- run_presentation_agent(self)
230
-
231
-
232
- # --- Helper Functions ---
233
-
234
-
235
- def parse_plan(plan_content: str) -> list[dict[str, str]]:
236
- """Parses the Markdown plan content into a list of slide dictionaries."""
237
- slides: list[dict[str, str]] = []
238
- # Regex to find slide blocks, topics, and summaries
239
- slide_pattern = re.compile(
240
- r"# Slide (?P<number>\d+)\s*\n+Topic:\s*(?P<topic>.*?)\s*\n+Summary:\s*(?P<summary>.*?)(?=\n+# Slide|\Z)",
241
- re.DOTALL | re.IGNORECASE,
242
- )
243
- matches = slide_pattern.finditer(plan_content)
244
- for match in matches:
245
- slide_data = match.groupdict()
246
- slides.append({
247
- "number": slide_data["number"].strip(),
248
- "topic": slide_data["topic"].strip(),
249
- "summary": slide_data["summary"].strip(),
250
- })
251
- return slides
252
-
253
-
254
- def extract_html_script(response: str) -> tuple[str | None, str | None]:
255
- """Extracts HTML block and the following script from the AI response."""
256
- html_match = re.search(r"```html\s*(.*?)\s*```", response, re.DOTALL)
257
- if not html_match:
258
- # Maybe the AI didn't use ```html, try finding <!DOCTYPE html>...</html>
259
- html_match = re.search(r"<!DOCTYPE html>.*?</html>", response, re.DOTALL | re.IGNORECASE)
260
- if not html_match:
261
- return None, response # Assume entire response might be script if no HTML found
262
-
263
- html_content = html_match.group(1) if len(html_match.groups()) > 0 else html_match.group(0)
264
- html_content = html_content.strip()
265
-
266
- # Script is assumed to be the text *after* the HTML block
267
- script_content = response[html_match.end() :].strip()
268
-
269
- return html_content, script_content if script_content else None
270
-
271
-
272
- def extract_slide_body(html_content: str) -> str:
273
- """Extracts the content within the <body> tags of a slide's HTML."""
274
- body_match = re.search(r"<body.*?>(.*?)</body>", html_content, re.DOTALL | re.IGNORECASE)
275
- if body_match:
276
- return body_match.group(1).strip()
277
- else:
278
- # Fallback: If no body tag, return the whole content assuming it's body-only
279
- # Remove potential head/style tags if they exist outside body
280
- html_content = re.sub(r"<head>.*?</head>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
281
- html_content = re.sub(r"<style>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
282
- return html_content.strip()
283
-
284
-
285
- # --- Main Agent Loop ---
286
-
287
-
288
- class GeneratedSlide(TypedDict):
289
- number: str
290
- html: str
291
- script: NotRequired[str]
292
-
293
-
294
- def run_presentation_agent(args: Arguments):
295
- """Executes the presentation generation agent loop."""
296
-
297
- if args.verbose:
298
- print(f"Initializing Presentation Agent with model: {args.provider}")
299
- print(f"Input file: {args.research_file}")
300
- print(f"Plan file: {args.plan_file}")
301
- print(f"Output file: {args.output_file}")
302
- print(f"Slides dir: {args.slides_dir}")
303
-
304
- # --- 1. Initialize Chatterer ---
305
- try:
306
- chatterer = Chatterer.from_provider(args.provider)
307
- except ValueError as e:
308
- print(f"Error initializing model: {e}")
309
- sys.exit(1)
310
- except Exception as e:
311
- print(f"An unexpected error occurred during model initialization: {e}")
312
- sys.exit(1)
313
-
314
- # --- 2. Read Input Research File ---
315
- input_path = Path(args.research_file)
316
- if not input_path.is_file():
317
- print(f"Error: Input file not found at '{args.research_file}'")
318
- sys.exit(1)
319
-
320
- try:
321
- research_content = input_path.read_text(encoding="utf-8")
322
- if args.verbose:
323
- print(f"Successfully read input file: {args.research_file}")
324
- except Exception as e:
325
- print(f"Error reading input file '{args.research_file}': {e}")
326
- sys.exit(1)
327
-
328
- # --- 3. Generate Plan ---
329
- plan_path = Path(args.plan_file)
330
- if args.verbose:
331
- print("\n--- Generating Presentation Plan ---")
332
-
333
- plan_prompt = args.job_prompt.format(research_content=research_content)
334
- messages = [HumanMessage(content=plan_prompt)]
335
-
336
- try:
337
- if args.verbose:
338
- print("Sending request to LLM for plan generation...")
339
- plan_content = chatterer(messages)
340
- if args.verbose:
341
- print("Received plan from LLM.")
342
-
343
- # Ensure the plan file's directory exists
344
- plan_path.parent.mkdir(parents=True, exist_ok=True)
345
- plan_path.write_text(plan_content, encoding="utf-8")
346
- if args.verbose:
347
- print(f"Presentation plan saved to: {args.plan_file}")
348
- # print(f"\nPlan Content:\n{plan_content[:500]}...\n") # Preview plan
349
- except Exception as e:
350
- print(f"Error during plan generation or saving: {e}")
351
- sys.exit(1)
352
-
353
- # --- 4. Parse Plan ---
354
- if args.verbose:
355
- print("\n--- Parsing Presentation Plan ---")
356
- try:
357
- slides_plan = parse_plan(plan_content)
358
- if not slides_plan:
359
- print("Error: Could not parse any slides from the generated plan. Check plan.md format.")
360
- sys.exit(1)
361
- if args.verbose:
362
- print(f"Successfully parsed {len(slides_plan)} slides from the plan.")
363
- except Exception as e:
364
- print(f"Error parsing plan file '{args.plan_file}': {e}")
365
- sys.exit(1)
366
-
367
- # --- 5. Generate Individual Slides ---
368
- if args.verbose:
369
- print("\n--- Generating Individual Slides & Scripts ---")
370
-
371
- slides_dir_path = Path(args.slides_dir)
372
- slides_dir_path.mkdir(parents=True, exist_ok=True)
373
- generated_slides_data: list[GeneratedSlide] = [] # Store {'html': ..., 'script': ...} for each slide
374
-
375
- system_message = SystemMessage(content=args.role_prompt)
376
-
377
- for i, slide_info in enumerate(slides_plan):
378
- slide_num = slide_info.get("number", str(i + 1))
379
- topic = slide_info.get("topic", "N/A")
380
- summary = slide_info.get("summary", "N/A")
381
-
382
- if args.verbose:
383
- print(f"\nGenerating Slide {slide_num}: {topic}")
384
-
385
- slide_gen_prompt = f"""\
386
- Generate the HTML/CSS slide and the speech script for the following slide based on the plan:
387
-
388
- Slide Number: {slide_num}
389
- Topic: {topic}
390
- Summary/Content Instructions:
391
- {summary}
392
-
393
- Remember to follow all instructions in the role prompt, especially regarding HTML structure (1280x720 .slide class, Tailwind, Font Awesome, Google Fonts via CDN, correct code block formatting) and output format (```html ... ``` block followed by the script).
394
- """
395
- messages: list[BaseMessage] = [system_message, HumanMessage(content=slide_gen_prompt)]
396
-
397
- try:
398
- if args.verbose:
399
- print("Sending request to LLM for slide generation...")
400
- response = chatterer(messages)
401
- if args.verbose:
402
- print("Received slide content from LLM.")
403
-
404
- html_content, script_content = extract_html_script(response)
405
-
406
- if not html_content:
407
- print(f"Warning: Could not extract HTML block for slide {slide_num}. Skipping.")
408
- continue
409
-
410
- slide_html_path = slides_dir_path / f"slide_{slide_num}.html"
411
- slide_script_path = slides_dir_path / f"slide_{slide_num}_script.txt"
412
-
413
- slide_html_path.write_text(html_content, encoding="utf-8")
414
- if args.verbose:
415
- print(f"Saved HTML for slide {slide_num} to: {slide_html_path}")
416
-
417
- if script_content:
418
- slide_script_path.write_text(script_content, encoding="utf-8")
419
- if args.verbose:
420
- print(f"Saved script for slide {slide_num} to: {slide_script_path}")
421
- else:
422
- if args.verbose:
423
- print(f"Warning: No script content found for slide {slide_num}.")
424
-
425
- generated_slides_data.append({"number": slide_num, "html": html_content, "script": script_content or ""})
426
-
427
- except Exception as e:
428
- print(f"Error generating slide {slide_num}: {e}")
429
- # Optionally continue to the next slide or exit
430
- # continue
431
- sys.exit(1)
432
-
433
- if not generated_slides_data:
434
- print("Error: No slides were successfully generated.")
435
- sys.exit(1)
436
-
437
- # --- 6. Organize Slides into Final Presentation ---
438
- if args.verbose:
439
- print("\n--- Organizing Slides into Final Presentation ---")
440
-
441
- output_path = Path(args.output_file)
442
-
443
- # Prepare the combined HTML content for the organization prompt
444
- # Extract only the body content of each slide
445
- all_slides_body_html = ""
446
- for slide_data in generated_slides_data:
447
- slide_body = extract_slide_body(slide_data["html"])
448
- all_slides_body_html += f"<!-- Slide {slide_data['number']} Content -->\n"
449
- all_slides_body_html += f"<div class='step' data-x='{int(slide_data['number']) * 1500}' data-y='0' data-scale='1'>\n" # Simple linear layout
450
- all_slides_body_html += slide_body
451
- all_slides_body_html += "\n</div>\n\n"
452
-
453
- organization_formatted_prompt = args.organization_prompt.format(all_slides_html=all_slides_body_html)
454
- messages = [HumanMessage(content=organization_formatted_prompt)]
455
-
456
- try:
457
- if args.verbose:
458
- print("Sending request to LLM for final presentation generation...")
459
- final_presentation_html = chatterer(messages)
460
-
461
- # Often models add ```html markdown, remove it if present
462
- final_presentation_html = re.sub(r"^```html\s*", "", final_presentation_html, flags=re.IGNORECASE)
463
- final_presentation_html = re.sub(r"\s*```$", "", final_presentation_html)
464
-
465
- if args.verbose:
466
- print("Received final presentation HTML from LLM.")
467
-
468
- # Ensure the output directory exists
469
- output_path.parent.mkdir(parents=True, exist_ok=True)
470
- output_path.write_text(final_presentation_html, encoding="utf-8")
471
- if args.verbose:
472
- print(f"Final presentation saved to: {args.output_file}")
473
-
474
- except Exception as e:
475
- print(f"Error during final presentation generation or saving: {e}")
476
- sys.exit(1)
477
-
478
- print("\n--- Presentation Generation Complete! ---")
479
-
480
-
481
- def main() -> None:
482
- Arguments().run()
483
-
484
-
485
- if __name__ == "__main__":
486
- main()
1
+ import re
2
+ import sys
3
+ from pathlib import Path
4
+ from typing import NotRequired, TypedDict
5
+
6
+ from spargear import RunnableArguments
7
+
8
+ from chatterer import BaseMessage, Chatterer, HumanMessage, SystemMessage
9
+ from chatterer.constants import DEFAULT_OPENAI_MODEL
10
+
11
+ # --- Default Prompts ---
12
+
13
+ DEFAULT_ROLE_PROMPT = """\
14
+ # Prompt Content
15
+
16
+ ## Role Prompt: AI for Generating Presentation Slides and Scripts
17
+
18
+ You are a professional AI assistant that generates presentation slides and corresponding speech scripts based on user-provided content. Your primary task is to create a visually appealing and highly informative single presentation slide using HTML/CSS, along with a natural and well-structured speech script to accompany the slide. You must pay close attention to avoid layout breakage due to unnecessary whitespace or line breaks in text content or code blocks.
19
+
20
+ [Core Features]
21
+
22
+ 1. **Slide Generation (HTML/CSS):**
23
+ * Analyze the user's input, extract key information, and structure it logically.
24
+ * **Text Normalization:** Before inserting text into HTML elements, remove unintended line breaks and excessive spacing caused by OCR or formatting errors. Ensure that sentences and list items flow naturally. (e.g., "Easy gram\\nmar:" → "Easy grammar:")
25
+ * Design the slide based on a 16:9 aspect ratio (1280x720 pixels). Use the `.slide` class to explicitly define this size.
26
+ * Use Tailwind CSS utilities (via CDN: `<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">`)—such as Grid, Flexbox, Padding, and Margin—to structure the layout. Prevent layout issues caused by long text lines or unwanted white space.
27
+ * Enhance visuals with Font Awesome icons. Include the CDN in the `<head>` section (`<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.4.0/css/all.min.css">`) and choose relevant icons appropriately.
28
+ * Use Korean Google Fonts: `<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">`
29
+ * **Code Block Handling:**
30
+ * Use `<pre><code>` tags for code samples. (e.g., `<div class="code-block bg-gray-800 text-white p-4 rounded mt-2"><pre><code>...code...</code></pre></div>`)
31
+ * Ensure that code lines inside `<code>` tags start without leading spaces to avoid rendering issues caused by HTML indentation.
32
+ * Optionally use `<span>` tags and classes like `comment`, `keyword`, or `string` for syntax highlighting.
33
+ * Carefully choose color, spacing, and typography to create a clean, professional layout.
34
+ * The generated HTML code must be fully self-contained, including all style information (Tailwind classes and optionally custom CSS via `<style>`) and CDN links. It should be enclosed in ```html ... ``` markdown blocks.
35
+
36
+ 2. **Speech Script Generation:**
37
+ * Write a clear and effective speech script to accompany the generated slide.
38
+ * Use a professional yet natural tone to ensure easy understanding for the audience.
39
+ * Explain each component of the slide (title, main points, code samples, etc.) in order, and include transitional or explanatory remarks where appropriate.
40
+ * Keep the speech concise but informative, covering all key points.
41
+ * Present the script clearly, outside of the HTML block.
42
+
43
+ [Workflow]
44
+
45
+ 1. Receive the user's input (topic or content for the presentation slide).
46
+ 2. Identify the key message, normalize the text, and structure it to fit into a single slide.
47
+ 3. Design and generate a 1280x720 slide using Tailwind CSS and Font Awesome. Pay special attention to code block formatting. Enclose the *complete* HTML for the slide within ```html ... ```.
48
+ 4. Write a natural speech script based on the slide's content. Place the script *after* the HTML block.
49
+ 5. Clearly separate the HTML code (within ```html ... ```) and the speech script.
50
+
51
+ [Guidelines]
52
+
53
+ * Always aim to generate a single, self-contained slide.
54
+ * Maintain high quality and structure similar to provided examples, but optimize the layout and content according to the input.
55
+ * Pay close attention to whitespace and line break handling in both source HTML and rendered output, especially for plain text and code blocks.
56
+ * Always use CDN links for external libraries (Tailwind, Font Awesome, Google Fonts).
57
+ * Write the code with readability and reusability in mind.
58
+ * The speech script must be tightly aligned with the slide content.
59
+ * **Output Format:** First provide the complete HTML code block: ```html <!DOCTYPE html>...</html> ```. Immediately following the HTML block, provide the speech script as plain text.
60
+
61
+ Incorrect Code Block HTML Example (To Avoid):
62
+
63
+ ```html
64
+ <!-- This renders unintended indentation due to <pre> preserving HTML source indentation -->
65
+ <div class="code-block">
66
+ <pre><code>
67
+ score = 85
68
+ if score >= 60:
69
+ print("Passed!")
70
+ else:
71
+ print("Failed.")
72
+ </code></pre>
73
+ </div>
74
+ ````
75
+
76
+ Correct Code Block HTML Example (Recommended):
77
+
78
+ ```html
79
+ <div class="code-block bg-gray-800 text-white p-4 rounded">
80
+ <pre><code>score = 85
81
+ if score >= 60:
82
+ print("Passed!") # Keep indentation within code block only
83
+ else:
84
+ print("Failed.")</code></pre>
85
+ </div>
86
+ ```"""
87
+
88
+ DEFAULT_JOB_PROMPT = """\
89
+ Objective: Analyze the content of the provided research material and create a detailed presentation slide plan in Markdown format. Save this plan to a file named 'plan.md'.
90
+
91
+ Detailed Guidelines:
92
+
93
+ 1. **Content Analysis:** Thoroughly analyze the provided research content to identify the core topic, main arguments, supporting data, conclusions, and other critical points.
94
+ 2. **Slide Structure:** Based on the analysis, organize the overall slide structure with a logical flow (e.g., Introduction Main Body Conclusion, or Problem – Solution). Aim for a reasonable number of slides to cover the material effectively without being overwhelming.
95
+ 3. **Slide-by-Slide Planning:** For each slide, define the content in detail. Each slide plan must include the following elements:
96
+ * **Slide Number:** Use the format `# Slide N`.
97
+ * **Topic:** Clearly state the central theme of the slide. (`Topic: ...`)
98
+ * **Summary:** Provide a concise summary of the key message, main content, relevant data, and possible visual ideas (like charts, key points, code snippets if applicable) to be included in the slide. (`Summary: ...`)
99
+ 4. **Output:** Generate *only* the Markdown content for the slide plan, following the format example below. Do not include any other explanatory text before or after the plan.
100
+
101
+ Output Format Example:
102
+
103
+ ```markdown
104
+ # Slide 1
105
+
106
+ Topic: Background and Rationale
107
+ Summary: Explain why the [research topic] is important and what current issues it addresses. Briefly reference relevant statistics or previous studies to highlight the need for this research. Consider using a compelling opening statistic or image.
108
+
109
+ # Slide 2
110
+
111
+ Topic: Research Objectives and Questions
112
+ Summary: Clearly state the specific objectives of the research (e.g., To investigate X, To develop Y). Present 1–2 concise research questions that the study aims to answer. Use bullet points for clarity.
113
+
114
+ # Slide 3
115
+
116
+ Topic: Methodology
117
+ Summary: Describe the research methods used (e.g., surveys, literature review, experiments, data analysis techniques). Summarize the data collection process and key parameters. Maybe include a simple flowchart icon.
118
+
119
+ ... (and so on for all necessary slides) ...
120
+
121
+ # Slide N
122
+
123
+ Topic: Conclusion and Recommendations
124
+ Summary: Summarize the key research findings and present the main conclusions clearly. Mention any significant limitations. Suggest directions for future research or practical recommendations based on the findings. End with a clear take-away message.
125
+ ```
126
+
127
+ --- START OF RESEARCH MATERIAL ---
128
+ {research_content}
129
+ --- END OF RESEARCH MATERIAL ---
130
+
131
+ Now, generate the slide plan based *only* on the provided research material.
132
+ """
133
+
134
+ DEFAULT_ORGANIZATION_PROMPT = """\
135
+ Objective: Create a final `presentation.html` file using impress.js (loaded via CDN) to structure the provided individual HTML slide contents into a cohesive presentation.
136
+
137
+ Guidelines:
138
+
139
+ 1. **Structure:** Use the standard impress.js HTML structure.
140
+ * Include the impress.js library and its default CSS via CDN in the `<head>`.
141
+ * Each slide's HTML content should be placed inside a `<div class="step">` element within the `<div id="impress">` container.
142
+ * Assign appropriate `data-x`, `data-y`, `data-scale`, `data-rotate`, etc., attributes to the `step` divs to create a logical flow and visual transitions between slides. A simple linear flow (increasing `data-x` for each step) is acceptable, but feel free to add minor variations if it enhances the presentation flow.
143
+ 2. **Content Integration:** Embed the *full* HTML content provided for each slide within its corresponding `<div class="step">`. Ensure the slide content (including its own `<head>` elements like Tailwind CSS links if they were generated per-slide) is correctly placed *inside* the `step` div. It might be better to consolidate CSS/Font links into the main HTML `<head>`. Let's aim to consolidate the CSS/Font links in the main `<head>` and put only the `<body>` content of each slide inside the `<div class="step">`.
144
+ 3. **impress.js Initialization:** Include the `impress().init();` script at the end of the `<body>`.
145
+ 4. **Output:** Generate *only* the complete HTML code for the `presentation.html` file. Do not include any other explanatory text.
146
+
147
+ --- START OF SLIDE HTML CONTENTS ---
148
+
149
+ {all_slides_html}
150
+
151
+ --- END OF SLIDE HTML CONTENTS ---
152
+
153
+ Now, generate the final `presentation.html` file using impress.js and the provided slide contents.
154
+ """
155
+
156
+ # --- Argument Parsing ---
157
+
158
+
159
+ class Arguments(RunnableArguments[None]):
160
+ """
161
+ Arguments for the presentation generation process.
162
+ """
163
+
164
+ # Input file paths
165
+ research_file: str = "research.md"
166
+ """Path to the input research file"""
167
+ plan_file: str = "plan.md"
168
+ """Path to the slide plan file"""
169
+ output_file: str = "presentation.html"
170
+ """Path to the output presentation file"""
171
+ slides_dir: str = "slides"
172
+ """Directory to save individual slide files"""
173
+
174
+ # Prompt templates
175
+ role_prompt: str = DEFAULT_ROLE_PROMPT
176
+ """Role prompt for the AI assistant"""
177
+ job_prompt: str = DEFAULT_JOB_PROMPT
178
+ """Job prompt for content analysis and slide planning"""
179
+ organization_prompt: str = DEFAULT_ORGANIZATION_PROMPT
180
+ """Prompt for organizing slides into a presentation script"""
181
+
182
+ # LLM Settings
183
+ provider: str = f"openai:{DEFAULT_OPENAI_MODEL}"
184
+ f"""Name of the language model to use (e.g. 'openai:{DEFAULT_OPENAI_MODEL}')."""
185
+
186
+ # Other settings
187
+ verbose: bool = True
188
+ """Flag for verbose output during processing"""
189
+
190
+ def run(self) -> None:
191
+ # Create a dummy input file if it doesn't exist for testing
192
+ if not Path(self.research_file).exists():
193
+ print(f"Creating dummy input file: {self.research_file}")
194
+ Path(self.research_file).write_text(
195
+ """\
196
+ # Research Paper: The Future of AI Assistants
197
+
198
+ ## Introduction
199
+ The field of Artificial Intelligence (AI) has seen exponential growth. AI assistants are becoming integrated into daily life. This research explores future trends.
200
+
201
+ ## Current State
202
+ Current assistants (Siri, Alexa, Google Assistant) primarily handle simple commands, Q&A, and basic task automation. They rely heavily on predefined scripts and cloud connectivity. NLP has improved significantly, but true contextual understanding remains a challenge.
203
+
204
+ ## Key Trends
205
+ 1. **Proactive Assistance:** Assistants will anticipate user needs.
206
+ 2. **Hyper-Personalization:** Tailoring responses and actions based on deep user understanding.
207
+ 3. **Multimodal Interaction:** Seamlessly integrating voice, text, vision, and gestures.
208
+ 4. **On-Device Processing:** Enhancing privacy and speed by reducing cloud dependency. Example: `model.run_locally()`
209
+ 5. **Emotional Intelligence:** Recognizing and responding appropriately to user emotions.
210
+
211
+ ## Challenges
212
+ * Data Privacy and Security
213
+ * Algorithmic Bias
214
+ * Computational Cost
215
+ * Maintaining User Trust
216
+
217
+ ## Conclusion
218
+ The future of AI assistants points towards highly personalized, proactive, and emotionally intelligent companions. Overcoming challenges related to privacy and bias is crucial for widespread adoption. Python code example:
219
+ ```python
220
+ def greet(user):
221
+ # Simple greeting
222
+ print(f"Hello, {user}! How can I assist today?")
223
+
224
+ greet("Developer")
225
+ ```
226
+ """,
227
+ encoding="utf-8",
228
+ )
229
+
230
+ run_presentation_agent(self)
231
+
232
+
233
+ # --- Helper Functions ---
234
+
235
+
236
+ def parse_plan(plan_content: str) -> list[dict[str, str]]:
237
+ """Parses the Markdown plan content into a list of slide dictionaries."""
238
+ slides: list[dict[str, str]] = []
239
+ # Regex to find slide blocks, topics, and summaries
240
+ slide_pattern = re.compile(
241
+ r"# Slide (?P<number>\d+)\s*\n+Topic:\s*(?P<topic>.*?)\s*\n+Summary:\s*(?P<summary>.*?)(?=\n+# Slide|\Z)",
242
+ re.DOTALL | re.IGNORECASE,
243
+ )
244
+ matches = slide_pattern.finditer(plan_content)
245
+ for match in matches:
246
+ slide_data = match.groupdict()
247
+ slides.append({
248
+ "number": slide_data["number"].strip(),
249
+ "topic": slide_data["topic"].strip(),
250
+ "summary": slide_data["summary"].strip(),
251
+ })
252
+ return slides
253
+
254
+
255
+ def extract_html_script(response: str) -> tuple[str | None, str | None]:
256
+ """Extracts HTML block and the following script from the AI response."""
257
+ html_match = re.search(r"```html\s*(.*?)\s*```", response, re.DOTALL)
258
+ if not html_match:
259
+ # Maybe the AI didn't use ```html, try finding <!DOCTYPE html>...</html>
260
+ html_match = re.search(r"<!DOCTYPE html>.*?</html>", response, re.DOTALL | re.IGNORECASE)
261
+ if not html_match:
262
+ return None, response # Assume entire response might be script if no HTML found
263
+
264
+ html_content = html_match.group(1) if len(html_match.groups()) > 0 else html_match.group(0)
265
+ html_content = html_content.strip()
266
+
267
+ # Script is assumed to be the text *after* the HTML block
268
+ script_content = response[html_match.end() :].strip()
269
+
270
+ return html_content, script_content if script_content else None
271
+
272
+
273
+ def extract_slide_body(html_content: str) -> str:
274
+ """Extracts the content within the <body> tags of a slide's HTML."""
275
+ body_match = re.search(r"<body.*?>(.*?)</body>", html_content, re.DOTALL | re.IGNORECASE)
276
+ if body_match:
277
+ return body_match.group(1).strip()
278
+ else:
279
+ # Fallback: If no body tag, return the whole content assuming it's body-only
280
+ # Remove potential head/style tags if they exist outside body
281
+ html_content = re.sub(r"<head>.*?</head>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
282
+ html_content = re.sub(r"<style>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
283
+ return html_content.strip()
284
+
285
+
286
+ # --- Main Agent Loop ---
287
+
288
+
289
+ class GeneratedSlide(TypedDict):
290
+ number: str
291
+ html: str
292
+ script: NotRequired[str]
293
+
294
+
295
+ def run_presentation_agent(args: Arguments):
296
+ """Executes the presentation generation agent loop."""
297
+
298
+ if args.verbose:
299
+ print(f"Initializing Presentation Agent with model: {args.provider}")
300
+ print(f"Input file: {args.research_file}")
301
+ print(f"Plan file: {args.plan_file}")
302
+ print(f"Output file: {args.output_file}")
303
+ print(f"Slides dir: {args.slides_dir}")
304
+
305
+ # --- 1. Initialize Chatterer ---
306
+ try:
307
+ chatterer = Chatterer.from_provider(args.provider)
308
+ except ValueError as e:
309
+ print(f"Error initializing model: {e}")
310
+ sys.exit(1)
311
+ except Exception as e:
312
+ print(f"An unexpected error occurred during model initialization: {e}")
313
+ sys.exit(1)
314
+
315
+ # --- 2. Read Input Research File ---
316
+ input_path = Path(args.research_file)
317
+ if not input_path.is_file():
318
+ print(f"Error: Input file not found at '{args.research_file}'")
319
+ sys.exit(1)
320
+
321
+ try:
322
+ research_content = input_path.read_text(encoding="utf-8")
323
+ if args.verbose:
324
+ print(f"Successfully read input file: {args.research_file}")
325
+ except Exception as e:
326
+ print(f"Error reading input file '{args.research_file}': {e}")
327
+ sys.exit(1)
328
+
329
+ # --- 3. Generate Plan ---
330
+ plan_path = Path(args.plan_file)
331
+ if args.verbose:
332
+ print("\n--- Generating Presentation Plan ---")
333
+
334
+ plan_prompt = args.job_prompt.format(research_content=research_content)
335
+ messages = [HumanMessage(content=plan_prompt)]
336
+
337
+ try:
338
+ if args.verbose:
339
+ print("Sending request to LLM for plan generation...")
340
+ plan_content = chatterer(messages)
341
+ if args.verbose:
342
+ print("Received plan from LLM.")
343
+
344
+ # Ensure the plan file's directory exists
345
+ plan_path.parent.mkdir(parents=True, exist_ok=True)
346
+ plan_path.write_text(plan_content, encoding="utf-8")
347
+ if args.verbose:
348
+ print(f"Presentation plan saved to: {args.plan_file}")
349
+ # print(f"\nPlan Content:\n{plan_content[:500]}...\n") # Preview plan
350
+ except Exception as e:
351
+ print(f"Error during plan generation or saving: {e}")
352
+ sys.exit(1)
353
+
354
+ # --- 4. Parse Plan ---
355
+ if args.verbose:
356
+ print("\n--- Parsing Presentation Plan ---")
357
+ try:
358
+ slides_plan = parse_plan(plan_content)
359
+ if not slides_plan:
360
+ print("Error: Could not parse any slides from the generated plan. Check plan.md format.")
361
+ sys.exit(1)
362
+ if args.verbose:
363
+ print(f"Successfully parsed {len(slides_plan)} slides from the plan.")
364
+ except Exception as e:
365
+ print(f"Error parsing plan file '{args.plan_file}': {e}")
366
+ sys.exit(1)
367
+
368
+ # --- 5. Generate Individual Slides ---
369
+ if args.verbose:
370
+ print("\n--- Generating Individual Slides & Scripts ---")
371
+
372
+ slides_dir_path = Path(args.slides_dir)
373
+ slides_dir_path.mkdir(parents=True, exist_ok=True)
374
+ generated_slides_data: list[GeneratedSlide] = [] # Store {'html': ..., 'script': ...} for each slide
375
+
376
+ system_message = SystemMessage(content=args.role_prompt)
377
+
378
+ for i, slide_info in enumerate(slides_plan):
379
+ slide_num = slide_info.get("number", str(i + 1))
380
+ topic = slide_info.get("topic", "N/A")
381
+ summary = slide_info.get("summary", "N/A")
382
+
383
+ if args.verbose:
384
+ print(f"\nGenerating Slide {slide_num}: {topic}")
385
+
386
+ slide_gen_prompt = f"""\
387
+ Generate the HTML/CSS slide and the speech script for the following slide based on the plan:
388
+
389
+ Slide Number: {slide_num}
390
+ Topic: {topic}
391
+ Summary/Content Instructions:
392
+ {summary}
393
+
394
+ Remember to follow all instructions in the role prompt, especially regarding HTML structure (1280x720 .slide class, Tailwind, Font Awesome, Google Fonts via CDN, correct code block formatting) and output format (```html ... ``` block followed by the script).
395
+ """
396
+ messages: list[BaseMessage] = [system_message, HumanMessage(content=slide_gen_prompt)]
397
+
398
+ try:
399
+ if args.verbose:
400
+ print("Sending request to LLM for slide generation...")
401
+ response = chatterer(messages)
402
+ if args.verbose:
403
+ print("Received slide content from LLM.")
404
+
405
+ html_content, script_content = extract_html_script(response)
406
+
407
+ if not html_content:
408
+ print(f"Warning: Could not extract HTML block for slide {slide_num}. Skipping.")
409
+ continue
410
+
411
+ slide_html_path = slides_dir_path / f"slide_{slide_num}.html"
412
+ slide_script_path = slides_dir_path / f"slide_{slide_num}_script.txt"
413
+
414
+ slide_html_path.write_text(html_content, encoding="utf-8")
415
+ if args.verbose:
416
+ print(f"Saved HTML for slide {slide_num} to: {slide_html_path}")
417
+
418
+ if script_content:
419
+ slide_script_path.write_text(script_content, encoding="utf-8")
420
+ if args.verbose:
421
+ print(f"Saved script for slide {slide_num} to: {slide_script_path}")
422
+ else:
423
+ if args.verbose:
424
+ print(f"Warning: No script content found for slide {slide_num}.")
425
+
426
+ generated_slides_data.append({"number": slide_num, "html": html_content, "script": script_content or ""})
427
+
428
+ except Exception as e:
429
+ print(f"Error generating slide {slide_num}: {e}")
430
+ # Optionally continue to the next slide or exit
431
+ # continue
432
+ sys.exit(1)
433
+
434
+ if not generated_slides_data:
435
+ print("Error: No slides were successfully generated.")
436
+ sys.exit(1)
437
+
438
+ # --- 6. Organize Slides into Final Presentation ---
439
+ if args.verbose:
440
+ print("\n--- Organizing Slides into Final Presentation ---")
441
+
442
+ output_path = Path(args.output_file)
443
+
444
+ # Prepare the combined HTML content for the organization prompt
445
+ # Extract only the body content of each slide
446
+ all_slides_body_html = ""
447
+ for slide_data in generated_slides_data:
448
+ slide_body = extract_slide_body(slide_data["html"])
449
+ all_slides_body_html += f"<!-- Slide {slide_data['number']} Content -->\n"
450
+ all_slides_body_html += f"<div class='step' data-x='{int(slide_data['number']) * 1500}' data-y='0' data-scale='1'>\n" # Simple linear layout
451
+ all_slides_body_html += slide_body
452
+ all_slides_body_html += "\n</div>\n\n"
453
+
454
+ organization_formatted_prompt = args.organization_prompt.format(all_slides_html=all_slides_body_html)
455
+ messages = [HumanMessage(content=organization_formatted_prompt)]
456
+
457
+ try:
458
+ if args.verbose:
459
+ print("Sending request to LLM for final presentation generation...")
460
+ final_presentation_html = chatterer(messages)
461
+
462
+ # Often models add ```html markdown, remove it if present
463
+ final_presentation_html = re.sub(r"^```html\s*", "", final_presentation_html, flags=re.IGNORECASE)
464
+ final_presentation_html = re.sub(r"\s*```$", "", final_presentation_html)
465
+
466
+ if args.verbose:
467
+ print("Received final presentation HTML from LLM.")
468
+
469
+ # Ensure the output directory exists
470
+ output_path.parent.mkdir(parents=True, exist_ok=True)
471
+ output_path.write_text(final_presentation_html, encoding="utf-8")
472
+ if args.verbose:
473
+ print(f"Final presentation saved to: {args.output_file}")
474
+
475
+ except Exception as e:
476
+ print(f"Error during final presentation generation or saving: {e}")
477
+ sys.exit(1)
478
+
479
+ print("\n--- Presentation Generation Complete! ---")
480
+
481
+
482
+ def main() -> None:
483
+ Arguments().run()
484
+
485
+
486
+ if __name__ == "__main__":
487
+ main()