chatterer 0.1.18__py3-none-any.whl → 0.1.19__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 (44) hide show
  1. chatterer/__init__.py +93 -93
  2. chatterer/common_types/__init__.py +21 -21
  3. chatterer/common_types/io.py +19 -19
  4. chatterer/examples/__init__.py +0 -0
  5. chatterer/examples/anything_to_markdown.py +95 -91
  6. chatterer/examples/get_code_snippets.py +64 -62
  7. chatterer/examples/login_with_playwright.py +171 -167
  8. chatterer/examples/make_ppt.py +499 -497
  9. chatterer/examples/pdf_to_markdown.py +107 -107
  10. chatterer/examples/pdf_to_text.py +60 -56
  11. chatterer/examples/transcription_api.py +127 -123
  12. chatterer/examples/upstage_parser.py +95 -100
  13. chatterer/examples/webpage_to_markdown.py +79 -79
  14. chatterer/interactive.py +354 -354
  15. chatterer/language_model.py +533 -533
  16. chatterer/messages.py +21 -21
  17. chatterer/strategies/__init__.py +13 -13
  18. chatterer/strategies/atom_of_thoughts.py +975 -975
  19. chatterer/strategies/base.py +14 -14
  20. chatterer/tools/__init__.py +46 -46
  21. chatterer/tools/caption_markdown_images.py +384 -384
  22. chatterer/tools/citation_chunking/__init__.py +3 -3
  23. chatterer/tools/citation_chunking/chunks.py +53 -53
  24. chatterer/tools/citation_chunking/citation_chunker.py +118 -118
  25. chatterer/tools/citation_chunking/citations.py +285 -285
  26. chatterer/tools/citation_chunking/prompt.py +157 -157
  27. chatterer/tools/citation_chunking/reference.py +26 -26
  28. chatterer/tools/citation_chunking/utils.py +138 -138
  29. chatterer/tools/convert_pdf_to_markdown.py +302 -302
  30. chatterer/tools/convert_to_text.py +447 -447
  31. chatterer/tools/upstage_document_parser.py +705 -705
  32. chatterer/tools/webpage_to_markdown.py +739 -739
  33. chatterer/tools/youtube.py +146 -146
  34. chatterer/utils/__init__.py +15 -15
  35. chatterer/utils/base64_image.py +285 -285
  36. chatterer/utils/bytesio.py +59 -59
  37. chatterer/utils/code_agent.py +237 -237
  38. chatterer/utils/imghdr.py +148 -148
  39. {chatterer-0.1.18.dist-info → chatterer-0.1.19.dist-info}/METADATA +392 -392
  40. chatterer-0.1.19.dist-info/RECORD +44 -0
  41. {chatterer-0.1.18.dist-info → chatterer-0.1.19.dist-info}/WHEEL +1 -1
  42. chatterer-0.1.19.dist-info/entry_points.txt +10 -0
  43. chatterer-0.1.18.dist-info/RECORD +0 -42
  44. {chatterer-0.1.18.dist-info → chatterer-0.1.19.dist-info}/top_level.txt +0 -0
@@ -1,497 +1,499 @@
1
- def resolve_import_path_and_get_logger():
2
- # ruff: noqa: E402
3
- import logging
4
- import sys
5
-
6
- if __name__ == "__main__" and "." not in sys.path:
7
- sys.path.append(".")
8
-
9
- logger = logging.getLogger(__name__)
10
- return logger
11
-
12
-
13
- logger = resolve_import_path_and_get_logger()
14
- import re
15
- import sys
16
- from pathlib import Path
17
- from typing import NotRequired, TypedDict
18
-
19
- from spargear import BaseArguments
20
-
21
- from chatterer import BaseMessage, Chatterer, HumanMessage, SystemMessage
22
-
23
- # --- Default Prompts ---
24
-
25
- DEFAULT_ROLE_PROMPT = """\
26
- # Prompt Content
27
-
28
- ## Role Prompt: AI for Generating Presentation Slides and Scripts
29
-
30
- 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.
31
-
32
- [Core Features]
33
-
34
- 1. **Slide Generation (HTML/CSS):**
35
- * Analyze the user's input, extract key information, and structure it logically.
36
- * **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:")
37
- * Design the slide based on a 16:9 aspect ratio (1280x720 pixels). Use the `.slide` class to explicitly define this size.
38
- * 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.
39
- * 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.
40
- * Use Korean Google Fonts: `<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">`
41
- * **Code Block Handling:**
42
- * 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>`)
43
- * Ensure that code lines inside `<code>` tags start without leading spaces to avoid rendering issues caused by HTML indentation.
44
- * Optionally use `<span>` tags and classes like `comment`, `keyword`, or `string` for syntax highlighting.
45
- * Carefully choose color, spacing, and typography to create a clean, professional layout.
46
- * 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.
47
-
48
- 2. **Speech Script Generation:**
49
- * Write a clear and effective speech script to accompany the generated slide.
50
- * Use a professional yet natural tone to ensure easy understanding for the audience.
51
- * Explain each component of the slide (title, main points, code samples, etc.) in order, and include transitional or explanatory remarks where appropriate.
52
- * Keep the speech concise but informative, covering all key points.
53
- * Present the script clearly, outside of the HTML block.
54
-
55
- [Workflow]
56
-
57
- 1. Receive the user's input (topic or content for the presentation slide).
58
- 2. Identify the key message, normalize the text, and structure it to fit into a single slide.
59
- 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 ... ```.
60
- 4. Write a natural speech script based on the slide's content. Place the script *after* the HTML block.
61
- 5. Clearly separate the HTML code (within ```html ... ```) and the speech script.
62
-
63
- [Guidelines]
64
-
65
- * Always aim to generate a single, self-contained slide.
66
- * Maintain high quality and structure similar to provided examples, but optimize the layout and content according to the input.
67
- * Pay close attention to whitespace and line break handling in both source HTML and rendered output, especially for plain text and code blocks.
68
- * Always use CDN links for external libraries (Tailwind, Font Awesome, Google Fonts).
69
- * Write the code with readability and reusability in mind.
70
- * The speech script must be tightly aligned with the slide content.
71
- * **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.
72
-
73
- Incorrect Code Block HTML Example (To Avoid):
74
-
75
- ```html
76
- <!-- This renders unintended indentation due to <pre> preserving HTML source indentation -->
77
- <div class="code-block">
78
- <pre><code>
79
- score = 85
80
- if score >= 60:
81
- print("Passed!")
82
- else:
83
- print("Failed.")
84
- </code></pre>
85
- </div>
86
- ````
87
-
88
- Correct Code Block HTML Example (Recommended):
89
-
90
- ```html
91
- <div class="code-block bg-gray-800 text-white p-4 rounded">
92
- <pre><code>score = 85
93
- if score >= 60:
94
- print("Passed!") # Keep indentation within code block only
95
- else:
96
- print("Failed.")</code></pre>
97
- </div>
98
- ```"""
99
-
100
- DEFAULT_JOB_PROMPT = """\
101
- 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'.
102
-
103
- Detailed Guidelines:
104
-
105
- 1. **Content Analysis:** Thoroughly analyze the provided research content to identify the core topic, main arguments, supporting data, conclusions, and other critical points.
106
- 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.
107
- 3. **Slide-by-Slide Planning:** For each slide, define the content in detail. Each slide plan must include the following elements:
108
- * **Slide Number:** Use the format `# Slide N`.
109
- * **Topic:** Clearly state the central theme of the slide. (`Topic: ...`)
110
- * **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: ...`)
111
- 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.
112
-
113
- Output Format Example:
114
-
115
- ```markdown
116
- # Slide 1
117
-
118
- Topic: Background and Rationale
119
- 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.
120
-
121
- # Slide 2
122
-
123
- Topic: Research Objectives and Questions
124
- 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.
125
-
126
- # Slide 3
127
-
128
- Topic: Methodology
129
- 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.
130
-
131
- ... (and so on for all necessary slides) ...
132
-
133
- # Slide N
134
-
135
- Topic: Conclusion and Recommendations
136
- 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.
137
- ```
138
-
139
- --- START OF RESEARCH MATERIAL ---
140
- {research_content}
141
- --- END OF RESEARCH MATERIAL ---
142
-
143
- Now, generate the slide plan based *only* on the provided research material.
144
- """
145
-
146
- DEFAULT_ORGANIZATION_PROMPT = """\
147
- 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.
148
-
149
- Guidelines:
150
-
151
- 1. **Structure:** Use the standard impress.js HTML structure.
152
- * Include the impress.js library and its default CSS via CDN in the `<head>`.
153
- * Each slide's HTML content should be placed inside a `<div class="step">` element within the `<div id="impress">` container.
154
- * 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.
155
- 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">`.
156
- 3. **impress.js Initialization:** Include the `impress().init();` script at the end of the `<body>`.
157
- 4. **Output:** Generate *only* the complete HTML code for the `presentation.html` file. Do not include any other explanatory text.
158
-
159
- --- START OF SLIDE HTML CONTENTS ---
160
-
161
- {all_slides_html}
162
-
163
- --- END OF SLIDE HTML CONTENTS ---
164
-
165
- Now, generate the final `presentation.html` file using impress.js and the provided slide contents.
166
- """
167
-
168
- # --- Argument Parsing ---
169
-
170
-
171
- class MakePptArguments(BaseArguments):
172
- """
173
- Arguments for the presentation generation process.
174
- """
175
-
176
- # Input file paths
177
- research_file: str = "research.md"
178
- """Path to the input research file"""
179
- plan_file: str = "plan.md"
180
- """Path to the slide plan file"""
181
- output_file: str = "presentation.html"
182
- """Path to the output presentation file"""
183
- slides_dir: str = "slides"
184
- """Directory to save individual slide files"""
185
-
186
- # Prompt templates
187
- role_prompt: str = DEFAULT_ROLE_PROMPT
188
- """Role prompt for the AI assistant"""
189
- job_prompt: str = DEFAULT_JOB_PROMPT
190
- """Job prompt for content analysis and slide planning"""
191
- organization_prompt: str = DEFAULT_ORGANIZATION_PROMPT
192
- """Prompt for organizing slides into a presentation script"""
193
-
194
- # LLM Settings
195
- provider: str = (
196
- "openai:gpt-4.1" # Example: "openai:gpt-4o", "anthropic:claude-3-haiku-20240307", "google:gemini-1.5-flash"
197
- )
198
- """Name of the language model to use (provider:model_name)"""
199
-
200
- # Other settings
201
- verbose: bool = True
202
- """Flag for verbose output during processing"""
203
-
204
- def run(self) -> None:
205
- # Create a dummy input file if it doesn't exist for testing
206
- if not Path(self.research_file).exists():
207
- print(f"Creating dummy input file: {self.research_file}")
208
- Path(self.research_file).write_text(
209
- """\
210
- # Research Paper: The Future of AI Assistants
211
-
212
- ## Introduction
213
- The field of Artificial Intelligence (AI) has seen exponential growth. AI assistants are becoming integrated into daily life. This research explores future trends.
214
-
215
- ## Current State
216
- 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.
217
-
218
- ## Key Trends
219
- 1. **Proactive Assistance:** Assistants will anticipate user needs.
220
- 2. **Hyper-Personalization:** Tailoring responses and actions based on deep user understanding.
221
- 3. **Multimodal Interaction:** Seamlessly integrating voice, text, vision, and gestures.
222
- 4. **On-Device Processing:** Enhancing privacy and speed by reducing cloud dependency. Example: `model.run_locally()`
223
- 5. **Emotional Intelligence:** Recognizing and responding appropriately to user emotions.
224
-
225
- ## Challenges
226
- * Data Privacy and Security
227
- * Algorithmic Bias
228
- * Computational Cost
229
- * Maintaining User Trust
230
-
231
- ## Conclusion
232
- 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:
233
- ```python
234
- def greet(user):
235
- # Simple greeting
236
- print(f"Hello, {user}! How can I assist today?")
237
-
238
- greet("Developer")
239
- ```
240
- """,
241
- encoding="utf-8",
242
- )
243
-
244
- run_presentation_agent(self)
245
-
246
-
247
- # --- Helper Functions ---
248
-
249
-
250
- def parse_plan(plan_content: str) -> list[dict[str, str]]:
251
- """Parses the Markdown plan content into a list of slide dictionaries."""
252
- slides: list[dict[str, str]] = []
253
- # Regex to find slide blocks, topics, and summaries
254
- slide_pattern = re.compile(
255
- r"# Slide (?P<number>\d+)\s*\n+Topic:\s*(?P<topic>.*?)\s*\n+Summary:\s*(?P<summary>.*?)(?=\n+# Slide|\Z)",
256
- re.DOTALL | re.IGNORECASE,
257
- )
258
- matches = slide_pattern.finditer(plan_content)
259
- for match in matches:
260
- slide_data = match.groupdict()
261
- slides.append({
262
- "number": slide_data["number"].strip(),
263
- "topic": slide_data["topic"].strip(),
264
- "summary": slide_data["summary"].strip(),
265
- })
266
- return slides
267
-
268
-
269
- def extract_html_script(response: str) -> tuple[str | None, str | None]:
270
- """Extracts HTML block and the following script from the AI response."""
271
- html_match = re.search(r"```html\s*(.*?)\s*```", response, re.DOTALL)
272
- if not html_match:
273
- # Maybe the AI didn't use ```html, try finding <!DOCTYPE html>...</html>
274
- html_match = re.search(r"<!DOCTYPE html>.*?</html>", response, re.DOTALL | re.IGNORECASE)
275
- if not html_match:
276
- return None, response # Assume entire response might be script if no HTML found
277
-
278
- html_content = html_match.group(1) if len(html_match.groups()) > 0 else html_match.group(0)
279
- html_content = html_content.strip()
280
-
281
- # Script is assumed to be the text *after* the HTML block
282
- script_content = response[html_match.end() :].strip()
283
-
284
- return html_content, script_content if script_content else None
285
-
286
-
287
- def extract_slide_body(html_content: str) -> str:
288
- """Extracts the content within the <body> tags of a slide's HTML."""
289
- body_match = re.search(r"<body.*?>(.*?)</body>", html_content, re.DOTALL | re.IGNORECASE)
290
- if body_match:
291
- return body_match.group(1).strip()
292
- else:
293
- # Fallback: If no body tag, return the whole content assuming it's body-only
294
- # Remove potential head/style tags if they exist outside body
295
- html_content = re.sub(r"<head>.*?</head>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
296
- html_content = re.sub(r"<style>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
297
- return html_content.strip()
298
-
299
-
300
- # --- Main Agent Loop ---
301
-
302
-
303
- class GeneratedSlide(TypedDict):
304
- number: str
305
- html: str
306
- script: NotRequired[str]
307
-
308
-
309
- def run_presentation_agent(args: MakePptArguments):
310
- """Executes the presentation generation agent loop."""
311
-
312
- if args.verbose:
313
- print(f"Initializing Presentation Agent with model: {args.provider}")
314
- print(f"Input file: {args.research_file}")
315
- print(f"Plan file: {args.plan_file}")
316
- print(f"Output file: {args.output_file}")
317
- print(f"Slides dir: {args.slides_dir}")
318
-
319
- # --- 1. Initialize Chatterer ---
320
- try:
321
- chatterer = Chatterer.from_provider(args.provider)
322
- except ValueError as e:
323
- print(f"Error initializing model: {e}")
324
- sys.exit(1)
325
- except Exception as e:
326
- print(f"An unexpected error occurred during model initialization: {e}")
327
- sys.exit(1)
328
-
329
- # --- 2. Read Input Research File ---
330
- input_path = Path(args.research_file)
331
- if not input_path.is_file():
332
- print(f"Error: Input file not found at '{args.research_file}'")
333
- sys.exit(1)
334
-
335
- try:
336
- research_content = input_path.read_text(encoding="utf-8")
337
- if args.verbose:
338
- print(f"Successfully read input file: {args.research_file}")
339
- except Exception as e:
340
- print(f"Error reading input file '{args.research_file}': {e}")
341
- sys.exit(1)
342
-
343
- # --- 3. Generate Plan ---
344
- plan_path = Path(args.plan_file)
345
- if args.verbose:
346
- print("\n--- Generating Presentation Plan ---")
347
-
348
- plan_prompt = args.job_prompt.format(research_content=research_content)
349
- messages = [HumanMessage(content=plan_prompt)]
350
-
351
- try:
352
- if args.verbose:
353
- print("Sending request to LLM for plan generation...")
354
- plan_content = chatterer(messages)
355
- if args.verbose:
356
- print("Received plan from LLM.")
357
-
358
- # Ensure the plan file's directory exists
359
- plan_path.parent.mkdir(parents=True, exist_ok=True)
360
- plan_path.write_text(plan_content, encoding="utf-8")
361
- if args.verbose:
362
- print(f"Presentation plan saved to: {args.plan_file}")
363
- # print(f"\nPlan Content:\n{plan_content[:500]}...\n") # Preview plan
364
- except Exception as e:
365
- print(f"Error during plan generation or saving: {e}")
366
- sys.exit(1)
367
-
368
- # --- 4. Parse Plan ---
369
- if args.verbose:
370
- print("\n--- Parsing Presentation Plan ---")
371
- try:
372
- slides_plan = parse_plan(plan_content)
373
- if not slides_plan:
374
- print("Error: Could not parse any slides from the generated plan. Check plan.md format.")
375
- sys.exit(1)
376
- if args.verbose:
377
- print(f"Successfully parsed {len(slides_plan)} slides from the plan.")
378
- except Exception as e:
379
- print(f"Error parsing plan file '{args.plan_file}': {e}")
380
- sys.exit(1)
381
-
382
- # --- 5. Generate Individual Slides ---
383
- if args.verbose:
384
- print("\n--- Generating Individual Slides & Scripts ---")
385
-
386
- slides_dir_path = Path(args.slides_dir)
387
- slides_dir_path.mkdir(parents=True, exist_ok=True)
388
- generated_slides_data: list[GeneratedSlide] = [] # Store {'html': ..., 'script': ...} for each slide
389
-
390
- system_message = SystemMessage(content=args.role_prompt)
391
-
392
- for i, slide_info in enumerate(slides_plan):
393
- slide_num = slide_info.get("number", str(i + 1))
394
- topic = slide_info.get("topic", "N/A")
395
- summary = slide_info.get("summary", "N/A")
396
-
397
- if args.verbose:
398
- print(f"\nGenerating Slide {slide_num}: {topic}")
399
-
400
- slide_gen_prompt = f"""\
401
- Generate the HTML/CSS slide and the speech script for the following slide based on the plan:
402
-
403
- Slide Number: {slide_num}
404
- Topic: {topic}
405
- Summary/Content Instructions:
406
- {summary}
407
-
408
- 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).
409
- """
410
- messages: list[BaseMessage] = [system_message, HumanMessage(content=slide_gen_prompt)]
411
-
412
- try:
413
- if args.verbose:
414
- print("Sending request to LLM for slide generation...")
415
- response = chatterer(messages)
416
- if args.verbose:
417
- print("Received slide content from LLM.")
418
-
419
- html_content, script_content = extract_html_script(response)
420
-
421
- if not html_content:
422
- print(f"Warning: Could not extract HTML block for slide {slide_num}. Skipping.")
423
- continue
424
-
425
- slide_html_path = slides_dir_path / f"slide_{slide_num}.html"
426
- slide_script_path = slides_dir_path / f"slide_{slide_num}_script.txt"
427
-
428
- slide_html_path.write_text(html_content, encoding="utf-8")
429
- if args.verbose:
430
- print(f"Saved HTML for slide {slide_num} to: {slide_html_path}")
431
-
432
- if script_content:
433
- slide_script_path.write_text(script_content, encoding="utf-8")
434
- if args.verbose:
435
- print(f"Saved script for slide {slide_num} to: {slide_script_path}")
436
- else:
437
- if args.verbose:
438
- print(f"Warning: No script content found for slide {slide_num}.")
439
-
440
- generated_slides_data.append({"number": slide_num, "html": html_content, "script": script_content or ""})
441
-
442
- except Exception as e:
443
- print(f"Error generating slide {slide_num}: {e}")
444
- # Optionally continue to the next slide or exit
445
- # continue
446
- sys.exit(1)
447
-
448
- if not generated_slides_data:
449
- print("Error: No slides were successfully generated.")
450
- sys.exit(1)
451
-
452
- # --- 6. Organize Slides into Final Presentation ---
453
- if args.verbose:
454
- print("\n--- Organizing Slides into Final Presentation ---")
455
-
456
- output_path = Path(args.output_file)
457
-
458
- # Prepare the combined HTML content for the organization prompt
459
- # Extract only the body content of each slide
460
- all_slides_body_html = ""
461
- for slide_data in generated_slides_data:
462
- slide_body = extract_slide_body(slide_data["html"])
463
- all_slides_body_html += f"<!-- Slide {slide_data['number']} Content -->\n"
464
- 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
465
- all_slides_body_html += slide_body
466
- all_slides_body_html += "\n</div>\n\n"
467
-
468
- organization_formatted_prompt = args.organization_prompt.format(all_slides_html=all_slides_body_html)
469
- messages = [HumanMessage(content=organization_formatted_prompt)]
470
-
471
- try:
472
- if args.verbose:
473
- print("Sending request to LLM for final presentation generation...")
474
- final_presentation_html = chatterer(messages)
475
-
476
- # Often models add ```html markdown, remove it if present
477
- final_presentation_html = re.sub(r"^```html\s*", "", final_presentation_html, flags=re.IGNORECASE)
478
- final_presentation_html = re.sub(r"\s*```$", "", final_presentation_html)
479
-
480
- if args.verbose:
481
- print("Received final presentation HTML from LLM.")
482
-
483
- # Ensure the output directory exists
484
- output_path.parent.mkdir(parents=True, exist_ok=True)
485
- output_path.write_text(final_presentation_html, encoding="utf-8")
486
- if args.verbose:
487
- print(f"Final presentation saved to: {args.output_file}")
488
-
489
- except Exception as e:
490
- print(f"Error during final presentation generation or saving: {e}")
491
- sys.exit(1)
492
-
493
- print("\n--- Presentation Generation Complete! ---")
494
-
495
-
496
- if __name__ == "__main__":
497
- MakePptArguments().run()
1
+ def resolve_import_path_and_get_logger():
2
+ # ruff: noqa: E402
3
+ import logging
4
+ import sys
5
+
6
+ if __name__ == "__main__" and "." not in sys.path:
7
+ sys.path.append(".")
8
+
9
+ logger = logging.getLogger(__name__)
10
+ return logger
11
+
12
+
13
+ logger = resolve_import_path_and_get_logger()
14
+ import re
15
+ import sys
16
+ from pathlib import Path
17
+ from typing import NotRequired, TypedDict
18
+
19
+ from spargear import BaseArguments
20
+
21
+ from chatterer import BaseMessage, Chatterer, HumanMessage, SystemMessage
22
+
23
+ # --- Default Prompts ---
24
+
25
+ DEFAULT_ROLE_PROMPT = """\
26
+ # Prompt Content
27
+
28
+ ## Role Prompt: AI for Generating Presentation Slides and Scripts
29
+
30
+ 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.
31
+
32
+ [Core Features]
33
+
34
+ 1. **Slide Generation (HTML/CSS):**
35
+ * Analyze the user's input, extract key information, and structure it logically.
36
+ * **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:")
37
+ * Design the slide based on a 16:9 aspect ratio (1280x720 pixels). Use the `.slide` class to explicitly define this size.
38
+ * 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.
39
+ * 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.
40
+ * Use Korean Google Fonts: `<link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700&display=swap" rel="stylesheet">`
41
+ * **Code Block Handling:**
42
+ * 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>`)
43
+ * Ensure that code lines inside `<code>` tags start without leading spaces to avoid rendering issues caused by HTML indentation.
44
+ * Optionally use `<span>` tags and classes like `comment`, `keyword`, or `string` for syntax highlighting.
45
+ * Carefully choose color, spacing, and typography to create a clean, professional layout.
46
+ * 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.
47
+
48
+ 2. **Speech Script Generation:**
49
+ * Write a clear and effective speech script to accompany the generated slide.
50
+ * Use a professional yet natural tone to ensure easy understanding for the audience.
51
+ * Explain each component of the slide (title, main points, code samples, etc.) in order, and include transitional or explanatory remarks where appropriate.
52
+ * Keep the speech concise but informative, covering all key points.
53
+ * Present the script clearly, outside of the HTML block.
54
+
55
+ [Workflow]
56
+
57
+ 1. Receive the user's input (topic or content for the presentation slide).
58
+ 2. Identify the key message, normalize the text, and structure it to fit into a single slide.
59
+ 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 ... ```.
60
+ 4. Write a natural speech script based on the slide's content. Place the script *after* the HTML block.
61
+ 5. Clearly separate the HTML code (within ```html ... ```) and the speech script.
62
+
63
+ [Guidelines]
64
+
65
+ * Always aim to generate a single, self-contained slide.
66
+ * Maintain high quality and structure similar to provided examples, but optimize the layout and content according to the input.
67
+ * Pay close attention to whitespace and line break handling in both source HTML and rendered output, especially for plain text and code blocks.
68
+ * Always use CDN links for external libraries (Tailwind, Font Awesome, Google Fonts).
69
+ * Write the code with readability and reusability in mind.
70
+ * The speech script must be tightly aligned with the slide content.
71
+ * **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.
72
+
73
+ Incorrect Code Block HTML Example (To Avoid):
74
+
75
+ ```html
76
+ <!-- This renders unintended indentation due to <pre> preserving HTML source indentation -->
77
+ <div class="code-block">
78
+ <pre><code>
79
+ score = 85
80
+ if score >= 60:
81
+ print("Passed!")
82
+ else:
83
+ print("Failed.")
84
+ </code></pre>
85
+ </div>
86
+ ````
87
+
88
+ Correct Code Block HTML Example (Recommended):
89
+
90
+ ```html
91
+ <div class="code-block bg-gray-800 text-white p-4 rounded">
92
+ <pre><code>score = 85
93
+ if score >= 60:
94
+ print("Passed!") # Keep indentation within code block only
95
+ else:
96
+ print("Failed.")</code></pre>
97
+ </div>
98
+ ```"""
99
+
100
+ DEFAULT_JOB_PROMPT = """\
101
+ 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'.
102
+
103
+ Detailed Guidelines:
104
+
105
+ 1. **Content Analysis:** Thoroughly analyze the provided research content to identify the core topic, main arguments, supporting data, conclusions, and other critical points.
106
+ 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.
107
+ 3. **Slide-by-Slide Planning:** For each slide, define the content in detail. Each slide plan must include the following elements:
108
+ * **Slide Number:** Use the format `# Slide N`.
109
+ * **Topic:** Clearly state the central theme of the slide. (`Topic: ...`)
110
+ * **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: ...`)
111
+ 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.
112
+
113
+ Output Format Example:
114
+
115
+ ```markdown
116
+ # Slide 1
117
+
118
+ Topic: Background and Rationale
119
+ 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.
120
+
121
+ # Slide 2
122
+
123
+ Topic: Research Objectives and Questions
124
+ 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.
125
+
126
+ # Slide 3
127
+
128
+ Topic: Methodology
129
+ 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.
130
+
131
+ ... (and so on for all necessary slides) ...
132
+
133
+ # Slide N
134
+
135
+ Topic: Conclusion and Recommendations
136
+ 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.
137
+ ```
138
+
139
+ --- START OF RESEARCH MATERIAL ---
140
+ {research_content}
141
+ --- END OF RESEARCH MATERIAL ---
142
+
143
+ Now, generate the slide plan based *only* on the provided research material.
144
+ """
145
+
146
+ DEFAULT_ORGANIZATION_PROMPT = """\
147
+ 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.
148
+
149
+ Guidelines:
150
+
151
+ 1. **Structure:** Use the standard impress.js HTML structure.
152
+ * Include the impress.js library and its default CSS via CDN in the `<head>`.
153
+ * Each slide's HTML content should be placed inside a `<div class="step">` element within the `<div id="impress">` container.
154
+ * 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.
155
+ 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">`.
156
+ 3. **impress.js Initialization:** Include the `impress().init();` script at the end of the `<body>`.
157
+ 4. **Output:** Generate *only* the complete HTML code for the `presentation.html` file. Do not include any other explanatory text.
158
+
159
+ --- START OF SLIDE HTML CONTENTS ---
160
+
161
+ {all_slides_html}
162
+
163
+ --- END OF SLIDE HTML CONTENTS ---
164
+
165
+ Now, generate the final `presentation.html` file using impress.js and the provided slide contents.
166
+ """
167
+
168
+ # --- Argument Parsing ---
169
+
170
+
171
+ class MakePptArguments(BaseArguments):
172
+ """
173
+ Arguments for the presentation generation process.
174
+ """
175
+
176
+ # Input file paths
177
+ research_file: str = "research.md"
178
+ """Path to the input research file"""
179
+ plan_file: str = "plan.md"
180
+ """Path to the slide plan file"""
181
+ output_file: str = "presentation.html"
182
+ """Path to the output presentation file"""
183
+ slides_dir: str = "slides"
184
+ """Directory to save individual slide files"""
185
+
186
+ # Prompt templates
187
+ role_prompt: str = DEFAULT_ROLE_PROMPT
188
+ """Role prompt for the AI assistant"""
189
+ job_prompt: str = DEFAULT_JOB_PROMPT
190
+ """Job prompt for content analysis and slide planning"""
191
+ organization_prompt: str = DEFAULT_ORGANIZATION_PROMPT
192
+ """Prompt for organizing slides into a presentation script"""
193
+
194
+ # LLM Settings
195
+ provider: str = "openai:gpt-4.1" # Example: "openai:gpt-4o", "anthropic:claude-3-haiku-20240307", "google:gemini-1.5-flash"
196
+ """Name of the language model to use (provider:model_name)"""
197
+
198
+ # Other settings
199
+ verbose: bool = True
200
+ """Flag for verbose output during processing"""
201
+
202
+ def run(self) -> None:
203
+ # Create a dummy input file if it doesn't exist for testing
204
+ if not Path(self.research_file).exists():
205
+ print(f"Creating dummy input file: {self.research_file}")
206
+ Path(self.research_file).write_text(
207
+ """\
208
+ # Research Paper: The Future of AI Assistants
209
+
210
+ ## Introduction
211
+ The field of Artificial Intelligence (AI) has seen exponential growth. AI assistants are becoming integrated into daily life. This research explores future trends.
212
+
213
+ ## Current State
214
+ 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.
215
+
216
+ ## Key Trends
217
+ 1. **Proactive Assistance:** Assistants will anticipate user needs.
218
+ 2. **Hyper-Personalization:** Tailoring responses and actions based on deep user understanding.
219
+ 3. **Multimodal Interaction:** Seamlessly integrating voice, text, vision, and gestures.
220
+ 4. **On-Device Processing:** Enhancing privacy and speed by reducing cloud dependency. Example: `model.run_locally()`
221
+ 5. **Emotional Intelligence:** Recognizing and responding appropriately to user emotions.
222
+
223
+ ## Challenges
224
+ * Data Privacy and Security
225
+ * Algorithmic Bias
226
+ * Computational Cost
227
+ * Maintaining User Trust
228
+
229
+ ## Conclusion
230
+ 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:
231
+ ```python
232
+ def greet(user):
233
+ # Simple greeting
234
+ print(f"Hello, {user}! How can I assist today?")
235
+
236
+ greet("Developer")
237
+ ```
238
+ """,
239
+ encoding="utf-8",
240
+ )
241
+
242
+ run_presentation_agent(self)
243
+
244
+
245
+ # --- Helper Functions ---
246
+
247
+
248
+ def parse_plan(plan_content: str) -> list[dict[str, str]]:
249
+ """Parses the Markdown plan content into a list of slide dictionaries."""
250
+ slides: list[dict[str, str]] = []
251
+ # Regex to find slide blocks, topics, and summaries
252
+ slide_pattern = re.compile(
253
+ r"# Slide (?P<number>\d+)\s*\n+Topic:\s*(?P<topic>.*?)\s*\n+Summary:\s*(?P<summary>.*?)(?=\n+# Slide|\Z)",
254
+ re.DOTALL | re.IGNORECASE,
255
+ )
256
+ matches = slide_pattern.finditer(plan_content)
257
+ for match in matches:
258
+ slide_data = match.groupdict()
259
+ slides.append({
260
+ "number": slide_data["number"].strip(),
261
+ "topic": slide_data["topic"].strip(),
262
+ "summary": slide_data["summary"].strip(),
263
+ })
264
+ return slides
265
+
266
+
267
+ def extract_html_script(response: str) -> tuple[str | None, str | None]:
268
+ """Extracts HTML block and the following script from the AI response."""
269
+ html_match = re.search(r"```html\s*(.*?)\s*```", response, re.DOTALL)
270
+ if not html_match:
271
+ # Maybe the AI didn't use ```html, try finding <!DOCTYPE html>...</html>
272
+ html_match = re.search(r"<!DOCTYPE html>.*?</html>", response, re.DOTALL | re.IGNORECASE)
273
+ if not html_match:
274
+ return None, response # Assume entire response might be script if no HTML found
275
+
276
+ html_content = html_match.group(1) if len(html_match.groups()) > 0 else html_match.group(0)
277
+ html_content = html_content.strip()
278
+
279
+ # Script is assumed to be the text *after* the HTML block
280
+ script_content = response[html_match.end() :].strip()
281
+
282
+ return html_content, script_content if script_content else None
283
+
284
+
285
+ def extract_slide_body(html_content: str) -> str:
286
+ """Extracts the content within the <body> tags of a slide's HTML."""
287
+ body_match = re.search(r"<body.*?>(.*?)</body>", html_content, re.DOTALL | re.IGNORECASE)
288
+ if body_match:
289
+ return body_match.group(1).strip()
290
+ else:
291
+ # Fallback: If no body tag, return the whole content assuming it's body-only
292
+ # Remove potential head/style tags if they exist outside body
293
+ html_content = re.sub(r"<head>.*?</head>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
294
+ html_content = re.sub(r"<style>.*?</style>", "", html_content, flags=re.DOTALL | re.IGNORECASE)
295
+ return html_content.strip()
296
+
297
+
298
+ # --- Main Agent Loop ---
299
+
300
+
301
+ class GeneratedSlide(TypedDict):
302
+ number: str
303
+ html: str
304
+ script: NotRequired[str]
305
+
306
+
307
+ def run_presentation_agent(args: MakePptArguments):
308
+ """Executes the presentation generation agent loop."""
309
+
310
+ if args.verbose:
311
+ print(f"Initializing Presentation Agent with model: {args.provider}")
312
+ print(f"Input file: {args.research_file}")
313
+ print(f"Plan file: {args.plan_file}")
314
+ print(f"Output file: {args.output_file}")
315
+ print(f"Slides dir: {args.slides_dir}")
316
+
317
+ # --- 1. Initialize Chatterer ---
318
+ try:
319
+ chatterer = Chatterer.from_provider(args.provider)
320
+ except ValueError as e:
321
+ print(f"Error initializing model: {e}")
322
+ sys.exit(1)
323
+ except Exception as e:
324
+ print(f"An unexpected error occurred during model initialization: {e}")
325
+ sys.exit(1)
326
+
327
+ # --- 2. Read Input Research File ---
328
+ input_path = Path(args.research_file)
329
+ if not input_path.is_file():
330
+ print(f"Error: Input file not found at '{args.research_file}'")
331
+ sys.exit(1)
332
+
333
+ try:
334
+ research_content = input_path.read_text(encoding="utf-8")
335
+ if args.verbose:
336
+ print(f"Successfully read input file: {args.research_file}")
337
+ except Exception as e:
338
+ print(f"Error reading input file '{args.research_file}': {e}")
339
+ sys.exit(1)
340
+
341
+ # --- 3. Generate Plan ---
342
+ plan_path = Path(args.plan_file)
343
+ if args.verbose:
344
+ print("\n--- Generating Presentation Plan ---")
345
+
346
+ plan_prompt = args.job_prompt.format(research_content=research_content)
347
+ messages = [HumanMessage(content=plan_prompt)]
348
+
349
+ try:
350
+ if args.verbose:
351
+ print("Sending request to LLM for plan generation...")
352
+ plan_content = chatterer(messages)
353
+ if args.verbose:
354
+ print("Received plan from LLM.")
355
+
356
+ # Ensure the plan file's directory exists
357
+ plan_path.parent.mkdir(parents=True, exist_ok=True)
358
+ plan_path.write_text(plan_content, encoding="utf-8")
359
+ if args.verbose:
360
+ print(f"Presentation plan saved to: {args.plan_file}")
361
+ # print(f"\nPlan Content:\n{plan_content[:500]}...\n") # Preview plan
362
+ except Exception as e:
363
+ print(f"Error during plan generation or saving: {e}")
364
+ sys.exit(1)
365
+
366
+ # --- 4. Parse Plan ---
367
+ if args.verbose:
368
+ print("\n--- Parsing Presentation Plan ---")
369
+ try:
370
+ slides_plan = parse_plan(plan_content)
371
+ if not slides_plan:
372
+ print("Error: Could not parse any slides from the generated plan. Check plan.md format.")
373
+ sys.exit(1)
374
+ if args.verbose:
375
+ print(f"Successfully parsed {len(slides_plan)} slides from the plan.")
376
+ except Exception as e:
377
+ print(f"Error parsing plan file '{args.plan_file}': {e}")
378
+ sys.exit(1)
379
+
380
+ # --- 5. Generate Individual Slides ---
381
+ if args.verbose:
382
+ print("\n--- Generating Individual Slides & Scripts ---")
383
+
384
+ slides_dir_path = Path(args.slides_dir)
385
+ slides_dir_path.mkdir(parents=True, exist_ok=True)
386
+ generated_slides_data: list[GeneratedSlide] = [] # Store {'html': ..., 'script': ...} for each slide
387
+
388
+ system_message = SystemMessage(content=args.role_prompt)
389
+
390
+ for i, slide_info in enumerate(slides_plan):
391
+ slide_num = slide_info.get("number", str(i + 1))
392
+ topic = slide_info.get("topic", "N/A")
393
+ summary = slide_info.get("summary", "N/A")
394
+
395
+ if args.verbose:
396
+ print(f"\nGenerating Slide {slide_num}: {topic}")
397
+
398
+ slide_gen_prompt = f"""\
399
+ Generate the HTML/CSS slide and the speech script for the following slide based on the plan:
400
+
401
+ Slide Number: {slide_num}
402
+ Topic: {topic}
403
+ Summary/Content Instructions:
404
+ {summary}
405
+
406
+ 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).
407
+ """
408
+ messages: list[BaseMessage] = [system_message, HumanMessage(content=slide_gen_prompt)]
409
+
410
+ try:
411
+ if args.verbose:
412
+ print("Sending request to LLM for slide generation...")
413
+ response = chatterer(messages)
414
+ if args.verbose:
415
+ print("Received slide content from LLM.")
416
+
417
+ html_content, script_content = extract_html_script(response)
418
+
419
+ if not html_content:
420
+ print(f"Warning: Could not extract HTML block for slide {slide_num}. Skipping.")
421
+ continue
422
+
423
+ slide_html_path = slides_dir_path / f"slide_{slide_num}.html"
424
+ slide_script_path = slides_dir_path / f"slide_{slide_num}_script.txt"
425
+
426
+ slide_html_path.write_text(html_content, encoding="utf-8")
427
+ if args.verbose:
428
+ print(f"Saved HTML for slide {slide_num} to: {slide_html_path}")
429
+
430
+ if script_content:
431
+ slide_script_path.write_text(script_content, encoding="utf-8")
432
+ if args.verbose:
433
+ print(f"Saved script for slide {slide_num} to: {slide_script_path}")
434
+ else:
435
+ if args.verbose:
436
+ print(f"Warning: No script content found for slide {slide_num}.")
437
+
438
+ generated_slides_data.append({"number": slide_num, "html": html_content, "script": script_content or ""})
439
+
440
+ except Exception as e:
441
+ print(f"Error generating slide {slide_num}: {e}")
442
+ # Optionally continue to the next slide or exit
443
+ # continue
444
+ sys.exit(1)
445
+
446
+ if not generated_slides_data:
447
+ print("Error: No slides were successfully generated.")
448
+ sys.exit(1)
449
+
450
+ # --- 6. Organize Slides into Final Presentation ---
451
+ if args.verbose:
452
+ print("\n--- Organizing Slides into Final Presentation ---")
453
+
454
+ output_path = Path(args.output_file)
455
+
456
+ # Prepare the combined HTML content for the organization prompt
457
+ # Extract only the body content of each slide
458
+ all_slides_body_html = ""
459
+ for slide_data in generated_slides_data:
460
+ slide_body = extract_slide_body(slide_data["html"])
461
+ all_slides_body_html += f"<!-- Slide {slide_data['number']} Content -->\n"
462
+ 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
463
+ all_slides_body_html += slide_body
464
+ all_slides_body_html += "\n</div>\n\n"
465
+
466
+ organization_formatted_prompt = args.organization_prompt.format(all_slides_html=all_slides_body_html)
467
+ messages = [HumanMessage(content=organization_formatted_prompt)]
468
+
469
+ try:
470
+ if args.verbose:
471
+ print("Sending request to LLM for final presentation generation...")
472
+ final_presentation_html = chatterer(messages)
473
+
474
+ # Often models add ```html markdown, remove it if present
475
+ final_presentation_html = re.sub(r"^```html\s*", "", final_presentation_html, flags=re.IGNORECASE)
476
+ final_presentation_html = re.sub(r"\s*```$", "", final_presentation_html)
477
+
478
+ if args.verbose:
479
+ print("Received final presentation HTML from LLM.")
480
+
481
+ # Ensure the output directory exists
482
+ output_path.parent.mkdir(parents=True, exist_ok=True)
483
+ output_path.write_text(final_presentation_html, encoding="utf-8")
484
+ if args.verbose:
485
+ print(f"Final presentation saved to: {args.output_file}")
486
+
487
+ except Exception as e:
488
+ print(f"Error during final presentation generation or saving: {e}")
489
+ sys.exit(1)
490
+
491
+ print("\n--- Presentation Generation Complete! ---")
492
+
493
+
494
+ def main() -> None:
495
+ MakePptArguments().run()
496
+
497
+
498
+ if __name__ == "__main__":
499
+ main()