├── .github └── workflows │ └── pylint.yml ├── .gitignore ├── README.md ├── chatppt.py ├── chatppt_ui.py ├── demo1.png ├── demo2.png ├── requirements.txt ├── ui.png ├── ui_demo_1.png └── ui_demo_2.png /.github/workflows/pylint.yml: -------------------------------------------------------------------------------- 1 | name: Pylint 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v3 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v3 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - name: Install dependencies 18 | run: | 19 | python -m pip install --upgrade pip 20 | pip install pylint 21 | - name: Analysing the code with pylint 22 | run: | 23 | pylint $(git ls-files '*.py') 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # the generate content 132 | *.pptx 133 | 134 | # Pipe files 135 | Pipfile 136 | Pipfile.lock 137 | 138 | # VSCode 139 | .vscode/ 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ChatPPT 2 | 3 | ChatPPT is a tool powered by chatgpt/ollama that helps you generate PPT/slide. It supports output in English and Chinese. 4 | 5 | ## Table of Contents 6 | 7 | - [What's New](#whats-new) 8 | - [What is ChatPPT](#what-is-chatppt) 9 | - [Requirements](#requirements) 10 | - [Installation](#installation) 11 | - [Usage](#usage) 12 | - [Contributing](#contributing) 13 | - [License](#license) 14 | 15 | ## What's New 16 | 17 | ChatPPT now supports Ollama and includes a sample UI. 18 | 19 | ![UI demo 1](ui_demo_1.png) 20 | ![UI demo 2](ui_demo_2.png) 21 | 22 | ## What is ChatPPT 23 | 24 | ChatPPT is powered by chatgpt/ollama. It can help you generate PPT/slide in English and Chinese. 25 | 26 | ![What is GPT | 600](demo1.png) 27 | ![什么是AWS | 400](demo2.png) 28 | 29 | ## Requirements 30 | 31 | Python 3.8.10 or higher 32 | 33 | ## Installation 34 | 35 | ### Ollama 36 | 37 | Follow the [guide](https://ollama.com/) to install ollama 38 | 39 | ### OpenAI 40 | 41 | Generate your OpenAI API key at 42 | 43 | ## Usage 44 | 45 | 1. Install requirements 46 | 47 | ``` 48 | pip install -r requirements.txt 49 | ``` 50 | 51 | 2. Start Streamlit 52 | 53 | ``` 54 | streamlit run chatppt_ui.py 55 | ``` 56 | 57 | 3. Open the Streamlit URL in your browser () 58 | 59 | ![UI](ui.png) 60 | 61 | > You can also use ChatPPT in the command line: 62 | 63 | ```bash 64 | > python chatppt.py -h 65 | usage: chatppt.py [-h] [-m {openai,ollama}] -t TOPIC [-k API_KEY] [-u OLLAMA_URL] [-o OLLAMA_MODEL] [-p PAGES] [-l {cn,en}] 66 | 67 | I am your PPT assistant, I can help to you generate PPT. 68 | 69 | options: 70 | -h, --help show this help message and exit 71 | -m {openai,ollama}, --ai_model {openai,ollama} 72 | Select the AI model 73 | -t TOPIC, --topic TOPIC 74 | Your topic name 75 | -k API_KEY, --api_key API_KEY 76 | Your api key file path 77 | -u OLLAMA_URL, --ollama_url OLLAMA_URL 78 | Your ollama url 79 | -o OLLAMA_MODEL, --ollama_model OLLAMA_MODEL 80 | Specify the Ollama model to use 81 | -p PAGES, --pages PAGES 82 | How many slides to generate 83 | -l {cn,en}, --language {cn,en} 84 | Output language 85 | -------------------------------------------------------------------------------- /chatppt.py: -------------------------------------------------------------------------------- 1 | import re 2 | import openai 3 | import json 4 | import argparse 5 | import time 6 | import random 7 | import datetime 8 | from ollama import Client 9 | from pptx import Presentation 10 | 11 | 12 | class ChatPPT: 13 | def __init__(self, ai_model, api_key, ollama_url=None, ollama_model=None): 14 | self.ai_model = ai_model 15 | self.api_key = api_key 16 | self.ollama_url = ollama_url 17 | self.ollama_model = ollama_model 18 | 19 | @staticmethod 20 | def robot_print(text): 21 | for char in text: 22 | print(char, end="", flush=True) 23 | time.sleep(random.randrange(1, 2) / 1000.0) 24 | print("\r") 25 | return text 26 | 27 | def chatppt(self, topic, pages, language): 28 | language_map = {"cn": "Chinese", "en": "English"} 29 | language_str = language_map[language] 30 | self.robot_print(f"I'm working hard to generate your PPT about {topic}.") 31 | self.robot_print("It may takes about a few minutes.") 32 | self.robot_print(f"Your PPT will be generated in {language_str}") 33 | output_format = self._get_output_format() 34 | messages = self._get_messages(topic, pages, language_str, output_format) 35 | content = self._get_content(messages) 36 | return self._parse_content(content) 37 | 38 | def _get_output_format(self): 39 | return { 40 | "title": "example title", 41 | "pages": [ 42 | { 43 | "title": "title for page 1", 44 | "content": [ 45 | { 46 | "title": "title for bullet 1", 47 | "description": "detail for bullet 1", 48 | }, 49 | { 50 | "title": "title for bullet 2", 51 | "description": "detail for bullet 2", 52 | }, 53 | { 54 | "title": "title for bullet 3", 55 | "description": "detail for bullet 3", 56 | }, 57 | ], 58 | }, 59 | { 60 | "title": "title for page 2", 61 | "content": [ 62 | { 63 | "title": "title for bullet 1", 64 | "description": "detail for bullet 1", 65 | }, 66 | { 67 | "title": "title for bullet 2", 68 | "description": "detail for bullet 2", 69 | }, 70 | ], 71 | }, 72 | ], 73 | } 74 | 75 | def _get_messages(self, topic, pages, language_str, output_format): 76 | return [ 77 | { 78 | "role": "user", 79 | "content": f"""I am preparing a presentation on {topic}. Please assist in generating an outline in JSON format, adhering to the specified format {json.dumps(output_format)}. The presentation should span {pages} pages, with as many bullet points as possible. The content should be returned in {language_str}. You must add content for each slide. For each slide, you must add at least 4 bullet. Please ensure the output is valid JSON match the RFC-8295 specification. Don't return any other message""", 80 | } 81 | ] 82 | 83 | def _get_content(self, messages): 84 | if self.ai_model == "openai": 85 | openai.api_key = self.api_key 86 | completion = openai.ChatCompletion.create( 87 | model="gpt-3.5-turbo", messages=messages 88 | ) 89 | return completion.choices[0].message.content 90 | elif self.ai_model == "ollama": 91 | if self.ollama_url is None: 92 | raise Exception("Ollama URL is required when ai_model is 'ollama'") 93 | client = Client(host=self.ollama_url) 94 | response = client.chat(model=self.ollama_model, messages=messages) 95 | return response["message"]["content"] 96 | 97 | def _parse_content(self, content): 98 | try: 99 | match = re.search(r"(\{.*\})", content, re.DOTALL) 100 | if match: 101 | content = match.groups()[0] 102 | return json.loads(content.strip()) 103 | except Exception as e: 104 | print(f"The response is not a valid JSON format: {e}") 105 | print("I'm a PPT assistant, your PPT generate failed, please retry later..") 106 | raise Exception("The LLM return invalid result, please retry later..") 107 | exit(1) 108 | 109 | def generate_ppt(self, content, template=None): 110 | ppt = Presentation() 111 | if template: 112 | ppt = Presentation(template) 113 | self.create_slides(ppt, content) 114 | ppt_name = self._get_ppt_name(content) 115 | ppt.save(ppt_name) 116 | self.robot_print("Generate done, enjoy!") 117 | self.robot_print(f"Your PPT: {ppt_name}") 118 | return ppt_name 119 | 120 | def create_slides(self, presentation, content): 121 | self.create_title_slide(presentation, content) 122 | pages = content.get("pages", []) 123 | self.robot_print(f"Your PPT has {len(pages)} pages.") 124 | for index, page in enumerate(pages): 125 | self.create_content_slide(presentation, page, index) 126 | 127 | def create_title_slide(self, presentation, content): 128 | title_slide_layout = presentation.slide_layouts[0] 129 | title_slide = presentation.slides.add_slide(title_slide_layout) 130 | title_slide.shapes.title.text = content.get("title", "") 131 | title_slide.placeholders[1].text = "Generated by ChatPPT" 132 | 133 | def create_content_slide(self, presentation, page, index): 134 | page_title = page.get("title", "") 135 | self.robot_print(f"Page {index+1}: {page_title}") 136 | bullet_slide_layout = presentation.slide_layouts[1] 137 | bullet_slide = presentation.slides.add_slide(bullet_slide_layout) 138 | bullet_slide.shapes.title.text = page_title 139 | self.add_bullets_to_slide(bullet_slide, page) 140 | 141 | def add_bullets_to_slide(self, slide, page): 142 | body_shape = slide.shapes.placeholders[1] 143 | for bullet in page.get("content", []): 144 | self.add_bullet(body_shape, bullet) 145 | 146 | def add_bullet(self, body_shape, bullet): 147 | bullet_title = bullet.get("title", "") 148 | bullet_description = bullet.get("description", "") 149 | self.add_paragraph(body_shape, bullet_title, level=1) 150 | self.add_paragraph(body_shape, bullet_description, level=2) 151 | 152 | def add_paragraph(self, body_shape, text, level): 153 | paragraph = body_shape.text_frame.add_paragraph() 154 | paragraph.text = text 155 | paragraph.level = level 156 | 157 | def _get_ppt_name(self, content): 158 | ppt_name = content.get("title", "") 159 | ppt_name = re.sub(r'[\\/:*?"<>|]', "", ppt_name) 160 | timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S") 161 | return f"{ppt_name}_{timestamp}.pptx" 162 | 163 | 164 | def main(): 165 | args = args_parser() 166 | chat_ppt = ChatPPT(args.ai_model, args.api_key, args.ollama_url, args.ollama_model) 167 | chat_ppt.robot_print("Hi, I am your PPT assistant.") 168 | ppt_content = chat_ppt.chatppt(args.topic, args.pages, args.language) 169 | chat_ppt.generate_ppt(ppt_content) 170 | 171 | 172 | def args_parser(): 173 | parser = argparse.ArgumentParser( 174 | description="I am your PPT assistant, I can help to you generate PPT." 175 | ) 176 | parser.add_argument( 177 | "-m", 178 | "--ai_model", 179 | choices=["openai", "ollama"], 180 | default="openai", 181 | help="Select the AI model", 182 | ) 183 | parser.add_argument( 184 | "-t", "--topic", type=str, required=True, help="Your topic name" 185 | ) 186 | parser.add_argument( 187 | "-k", "--api_key", type=str, default=".token", help="Your api key file path" 188 | ) 189 | parser.add_argument( 190 | "-u", 191 | "--ollama_url", 192 | type=str, 193 | default="http://localhost:11434", 194 | help="Your ollama url", 195 | ) 196 | parser.add_argument( 197 | "-o", 198 | "--ollama_model", 199 | type=str, 200 | default="llama3", 201 | help="Specify the Ollama model to use", 202 | ) 203 | parser.add_argument( 204 | "-p", 205 | "--pages", 206 | type=int, 207 | required=False, 208 | default=5, 209 | help="How many slides to generate", 210 | ) 211 | parser.add_argument( 212 | "-l", 213 | "--language", 214 | choices=["cn", "en"], 215 | default="en", 216 | required=False, 217 | help="Output language", 218 | ) 219 | args = parser.parse_args() 220 | if args.ai_model == "openai" and args.api_key == ".token": 221 | parser.error("--api_key is required when ai_model is 'openai'") 222 | return args 223 | 224 | 225 | if __name__ == "__main__": 226 | main() 227 | -------------------------------------------------------------------------------- /chatppt_ui.py: -------------------------------------------------------------------------------- 1 | import streamlit as st 2 | from chatppt import ChatPPT 3 | 4 | # Set up the Streamlit interface 5 | st.title("ChatPPT Generator") 6 | st.write("Generate a slide presentation using AI!") 7 | 8 | # User selects the AI model 9 | ai_model = st.selectbox("Select AI Model", ["openai", "ollama"]) 10 | 11 | # Depending on the AI model, different inputs are required 12 | if ai_model == "openai": 13 | api_key = st.text_input("Enter your OpenAI API Key") 14 | ollama_url = None 15 | ollama_model = None 16 | elif ai_model == "ollama": 17 | ollama_url = st.text_input("Enter your Ollama URL", "http://localhost:11434") 18 | ollama_model = st.text_input("Enter your Ollama Model", "llama3") 19 | api_key = None 20 | 21 | # User inputs for the presentation 22 | topic = st.text_input("Enter the topic for the presentation") 23 | num_slides = st.slider("Number of pages", 5, 20, 5) 24 | language = st.selectbox("Select language", ["en", "cn"]) 25 | 26 | # Button to generate the Slide 27 | generate_button = st.button("Generate Slide", disabled=False) 28 | 29 | # If the button is clicked, generate the Slide 30 | if generate_button: 31 | with st.spinner("Generating Slide..."): 32 | chat_ppt = ChatPPT(ai_model, api_key, ollama_url, ollama_model) 33 | try: 34 | ppt_content = chat_ppt.chatppt(topic, num_slides, language) 35 | except Exception as e: 36 | st.error(f"Error generating Slide: {e}") 37 | st.stop() 38 | 39 | # Display the title and number of slides 40 | title = ppt_content.get("title", "") 41 | st.title(f"Title: {title}") 42 | slides = ppt_content.get("pages", []) 43 | st.subheader(f"Your slide has {len(slides)} pages:") 44 | 45 | # Display the title of each slide 46 | for index, slide in enumerate(slides): 47 | st.markdown(f"- Slide {index+1}: {slide.get('title','')}") 48 | 49 | # Generate the Slide file 50 | try: 51 | ppt_file_name = chat_ppt.generate_ppt(ppt_content) 52 | except Exception as e: 53 | st.error(f"Error generating Slide: {e}") 54 | st.stop() 55 | 56 | # Provide a download link for the Slide 57 | st.write("Slide generated!") 58 | st.write("Download your Slide:") 59 | with open(ppt_file_name, "rb") as f: 60 | st.download_button("Download file", f, ppt_file_name) 61 | -------------------------------------------------------------------------------- /demo1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuiMi24/chatppt/e87571bfead9f607a29768ec1f24d1c46bb25f82/demo1.png -------------------------------------------------------------------------------- /demo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuiMi24/chatppt/e87571bfead9f607a29768ec1f24d1c46bb25f82/demo2.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.9.5 2 | aiosignal==1.3.1 3 | altair==5.3.0 4 | anyio==4.3.0 5 | attrs==23.2.0 6 | blinker==1.7.0 7 | cachetools==5.3.3 8 | certifi==2024.2.2 9 | charset-normalizer==3.3.2 10 | click==8.1.7 11 | colorama==0.4.6 12 | frozenlist==1.4.1 13 | gitdb==4.0.11 14 | GitPython==3.1.43 15 | h11==0.14.0 16 | httpcore==1.0.5 17 | httpx==0.27.0 18 | idna==3.7 19 | Jinja2==3.1.3 20 | jsonschema==4.21.1 21 | jsonschema-specifications==2023.12.1 22 | lxml==5.2.1 23 | markdown-it-py==3.0.0 24 | MarkupSafe==2.1.5 25 | mdurl==0.1.2 26 | multidict==6.0.5 27 | numpy==1.26.4 28 | ollama==0.1.8 29 | openai==0.27.2 30 | packaging==24.0 31 | pandas==2.2.2 32 | pillow==10.3.0 33 | protobuf==4.25.3 34 | pyarrow==16.0.0 35 | pydeck==0.8.1b0 36 | Pygments==2.17.2 37 | python-dateutil==2.9.0.post0 38 | python-pptx==0.6.21 39 | pytz==2024.1 40 | referencing==0.34.0 41 | requests==2.31.0 42 | rich==13.7.1 43 | rpds-py==0.18.0 44 | six==1.16.0 45 | smmap==5.0.1 46 | sniffio==1.3.1 47 | streamlit==1.33.0 48 | tenacity==8.2.3 49 | toml==0.10.2 50 | toolz==0.12.1 51 | tornado==6.4 52 | tqdm==4.66.2 53 | typing_extensions==4.11.0 54 | tzdata==2024.1 55 | urllib3==2.2.1 56 | watchdog==4.0.0 57 | XlsxWriter==3.2.0 58 | yarl==1.9.4 59 | -------------------------------------------------------------------------------- /ui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuiMi24/chatppt/e87571bfead9f607a29768ec1f24d1c46bb25f82/ui.png -------------------------------------------------------------------------------- /ui_demo_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuiMi24/chatppt/e87571bfead9f607a29768ec1f24d1c46bb25f82/ui_demo_1.png -------------------------------------------------------------------------------- /ui_demo_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HuiMi24/chatppt/e87571bfead9f607a29768ec1f24d1c46bb25f82/ui_demo_2.png --------------------------------------------------------------------------------