This blog post demonstrates how to build a PowerPoint presentation generator using a locally running LLM (Deepseek‑r1:8b) via ChatOllama, along with a Markdown‐formatted Table of Contents. The presentation is created in a wide (16:9) format with auto-adjusted text.
pip install streamlit python-pptx langchain
The code imports necessary modules, including Streamlit, python-pptx, and regex. It then configures the LLM (Deepseek‑r1:8b) via ChatOllama running locally.
import streamlit as st
from pptx import Presentation
from pptx.util import Inches, Pt
from langchain_ollama.chat_models import ChatOllama
import re
# LLM Configuration (Deepseek-r1:8b running locally)
llm = ChatOllama(
model="deepseek-r1:8b",
base_url="http://127.0.0.1:11434" # Update with your LLM server details
)
The clean_toc_md
function removes any extraneous text—including any llm <think or reasoning>
blocks—and returns only the numbered Markdown list starting from the first line beginning with "1."
def clean_toc_md(raw_toc):
"""
Remove extraneous text and any text between and tags,
then extract only the numbered list starting with "1."
"""
# Remove text between and tags (including the tags)
cleaned_text = re.sub(r'.*? ', '', raw_toc, flags=re.DOTALL | re.IGNORECASE)
lines = cleaned_text.splitlines()
start_index = 0
for i, line in enumerate(lines):
if line.strip().startswith("1."):
start_index = i
break
cleaned_lines = []
for line in lines[start_index:]:
cleaned_line = line.lstrip("-* ").strip()
cleaned_lines.append(cleaned_line)
return "\n".join(cleaned_lines)
The generate_toc
function sends a prompt to the LLM to produce a professional, numbered Markdown Table of Contents for the presentation on the given topic. It then cleans the output.
def generate_toc(topic):
prompt = (
f"Generate a structured Table of Contents in Markdown format for a PowerPoint presentation on the topic: {topic}. "
"Output only a numbered list of section titles with no additional commentary or reasoning."
)
response = llm.invoke(prompt)
toc_raw = response.content
toc_md = clean_toc_md(toc_raw)
return toc_md
The create_ppt
function creates a wide-format (16:9) PowerPoint presentation. It builds a title slide and splits the Markdown Table of Contents into multiple slides (8 lines per slide) while enforcing a default font size of 26 (to fit within the slide boundaries).
def create_ppt(topic, toc_md, ppt_name):
ppt = Presentation()
# Set slide dimensions to 16:9 wide format
ppt.slide_width = Inches(13.33)
ppt.slide_height = Inches(7.5)
# Title Slide
title_slide_layout = ppt.slide_layouts[0]
slide = ppt.slides.add_slide(title_slide_layout)
slide.shapes.title.text = topic
# Process the TOC Markdown into lines
toc_lines = toc_md.splitlines()
lines_per_slide = 8 # Adjust as needed for clean formatting
num_slides = (len(toc_lines) + lines_per_slide - 1) // lines_per_slide
# Create one or more slides for the Table of Contents
for i in range(num_slides):
slide_layout = ppt.slide_layouts[1] # Title and Content layout
slide = ppt.slides.add_slide(slide_layout)
slide.shapes.title.text = "Table of Contents" if i == 0 else "Table of Contents (contd.)"
start = i * lines_per_slide
end = min((i + 1) * lines_per_slide, len(toc_lines))
content_text = "\n".join(toc_lines[start:end])
placeholder = slide.placeholders[1]
placeholder.text = content_text
# Adjust text formatting: force font size to 26 and auto-fit text if available.
try:
from pptx.enum.text import MSO_AUTO_SIZE
placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
except Exception:
pass
for paragraph in placeholder.text_frame.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(26)
if not ppt_name.lower().endswith(".pptx"):
ppt_name += ".pptx"
ppt.save(ppt_name)
return ppt_name
The Streamlit interface allows the user to enter a topic, generate the Table of Contents, edit the content if needed, suggest a default PPT name, and finally generate the presentation with a progress bar. A download button is provided to retrieve the generated PPT.
# Streamlit Interface
st.title("AI-Generated PowerPoint Creator")
st.subheader("Generate a professional, wide-format presentation from your topic using a Markdown Table of Contents.")
# 1. Enter Topic: a text box for long topics.
topic = st.text_area("Enter Topic:", height=300, value="Generative AI in Cybersecurity")
# 2. Generate Table of Contents
if st.button("Generate Table of Contents"):
if topic.strip() == "":
st.error("Please enter a topic first.")
else:
toc_md = generate_toc(topic)
st.session_state.toc_md = toc_md
st.success("Table of Contents generated.")
# Editable Table of Contents (Markdown Format)
if "toc_md" in st.session_state:
toc_md_edit = st.text_area("Table of Contents (Markdown Format):", st.session_state.toc_md, height=300)
st.session_state.toc_md = toc_md_edit
# 3. Enter PPT Name: suggest a default based on the topic (editable).
default_ppt_name = "Presentation_" + "_".join(topic.split()[:5]) if topic.strip() != "" else "Default_Presentation"
ppt_name = st.text_input("Enter PPT Name:", value=default_ppt_name)
# 4. Generate Presentation with Progress Bar
if st.button("Generate Presentation"):
if topic.strip() == "":
st.error("Please enter a topic.")
elif "toc_md" not in st.session_state or st.session_state.toc_md.strip() == "":
st.error("Please generate the Table of Contents first.")
else:
progress_bar = st.progress(0)
progress_bar.progress(20)
toc_md_final = st.session_state.toc_md
progress_bar.progress(50)
ppt_file_name = create_ppt(topic, toc_md_final, ppt_name)
progress_bar.progress(100)
st.success("Presentation generated successfully!")
with open(ppt_file_name, "rb") as file:
st.download_button(
label=f"Download {ppt_file_name}",
data=file,
file_name=ppt_file_name,
mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
)
import streamlit as st
from pptx import Presentation
from pptx.util import Inches, Pt
from langchain_ollama.chat_models import ChatOllama
import re
# -------------------------------
# LLM Configuration (Deepseek-r1:8b running locally)
# -------------------------------
llm = ChatOllama(
model="deepseek-r1:8b",
base_url="http://127.0.0.1:11434" # Update with your LLM server details
)
# -------------------------------
# Function to clean LLM output and extract only the Markdown numbered list
# -------------------------------
def clean_toc_md(raw_toc):
"""
Remove extraneous text (such as reasoning) that appears between and tags,
and extract only the numbered list. It finds the first line that starts with "1."
and returns all content from that line onward.
"""
# Remove all text between and (inclusive), using DOTALL to match newlines.
cleaned_text = re.sub(r'.*? ', '', raw_toc, flags=re.DOTALL | re.IGNORECASE)
lines = cleaned_text.splitlines()
start_index = 0
for i, line in enumerate(lines):
if line.strip().startswith("1."):
start_index = i
break
cleaned_lines = lines[start_index:]
return "\n".join(cleaned_lines)
# -------------------------------
# Function to generate Table of Contents in Markdown format
# -------------------------------
def generate_toc(topic):
prompt = (
f"Generate text contents in PPT style , for a PowerPoint presentation, on the topic: {topic}. "
f"Output only content relevant to {topic} along with titles, without any reasoning or commentary from LLM"
)
response = llm.invoke(prompt)
toc_raw = response.content
toc_md = clean_toc_md(toc_raw)
return toc_md
# -------------------------------
# Function to create PowerPoint slides from the Markdown TOC (Wide Format)
# -------------------------------
def create_ppt(topic, toc_md, ppt_name):
ppt = Presentation()
# Set slide dimensions to 16:9 wide format
ppt.slide_width = Inches(13.33)
ppt.slide_height = Inches(7.5)
# Title Slide
title_slide_layout = ppt.slide_layouts[0]
slide = ppt.slides.add_slide(title_slide_layout)
slide.shapes.title.text = topic
# Process the TOC Markdown into lines
toc_lines = toc_md.splitlines()
lines_per_slide = 8 # Adjust as needed for clean formatting
num_slides = (len(toc_lines) + lines_per_slide - 1) // lines_per_slide
# Create one or more slides for the Table of Contents
for i in range(num_slides):
slide_layout = ppt.slide_layouts[1] # Title and Content layout
slide = ppt.slides.add_slide(slide_layout)
slide.shapes.title.text = f"Title-{i}"
start = i * lines_per_slide
end = min((i + 1) * lines_per_slide, len(toc_lines))
content_text = "\n".join(toc_lines[start:end])
placeholder = slide.placeholders[1]
placeholder.text = content_text
# Adjust text formatting: set auto size if available and force font size to 26
try:
from pptx.enum.text import MSO_AUTO_SIZE
placeholder.text_frame.auto_size = MSO_AUTO_SIZE.TEXT_TO_FIT_SHAPE
except Exception:
pass
for paragraph in placeholder.text_frame.paragraphs:
for run in paragraph.runs:
run.font.size = Pt(26)
if not ppt_name.lower().endswith(".pptx"):
ppt_name += ".pptx"
ppt.save(ppt_name)
return ppt_name
# -------------------------------
# Streamlit Interface
# -------------------------------
st.title("AI-Generated PowerPoint Creator")
st.subheader("Generate a professional, wide-format presentation from your topic")
# 1. Enter Topic: a text box for long topics.
topic = st.text_area("Enter Topic:", height=100, value="Describe the topic for PPT")
# 2. Generate Content
if st.button("Generate Content"):
if topic.strip() == "":
st.error("Please enter a topic first.")
else:
toc_md = generate_toc(topic)
st.session_state.toc_md = toc_md
st.success("Content generated.")
# Editable generated contents (Markdown Format)
if "toc_md" in st.session_state:
toc_md_edit = st.text_area("Generate Content :", st.session_state.toc_md, height=2000)
st.session_state.toc_md = toc_md_edit
# 3. Enter PPT Name: suggest a default based on the topic (editable).
default_ppt_name = "PPT_" + "_".join(topic.split()[:5]) if topic.strip() != "" else "Default_Presentation"
ppt_name = st.text_input("Enter PPT Name:", value=default_ppt_name)
# 4. Generate Presentation with Progress Bar
if st.button("Generate Presentation"):
if topic.strip() == "":
st.error("Please enter a topic.")
elif "toc_md" not in st.session_state or st.session_state.toc_md.strip() == "":
st.error("Please generate the Table of Contents first.")
else:
progress_bar = st.progress(0)
progress_bar.progress(20)
toc_md_final = st.session_state.toc_md
progress_bar.progress(50)
ppt_file_name = create_ppt(topic, toc_md_final, ppt_name)
progress_bar.progress(100)
st.success("Presentation generated successfully!")
with open(ppt_file_name, "rb") as file:
st.download_button(
label=f"Download {ppt_file_name}",
data=file,
file_name=ppt_file_name,
mime="application/vnd.openxmlformats-officedocument.presentationml.presentation"
)
This is the complete code for the PPT Generator using locally running LLM
To run this PowerPoint Generator using Streamlit, open your terminal, navigate to the project directory, and run:
streamlit run PPT_Generator.py
This command launches an interactive web app where you can enter a topic, generate a Markdown Table of Contents, and create a presentation for download.
The PPT_Generator.py
program demonstrates how to integrate a locally running LLM (Deepseek‑r1:8b), along with Streamlit and python-pptx, to generate a professional, wide-format PowerPoint presentation. The app converts a clean, Markdown-formatted Table of Contents into multiple PPT slides with properly adjusted text to ensure readability.
Note: This application has significant potential for further enhancements. By leveraging Retrieval-Augmented Generation (RAG) and vector databases, users could incorporate private data—such as custom text, images, and charts—to generate highly tailored and controlled PPT slides.
PPT_Generative_AI_impacts_in_software.pptx