Upload 3 files
Browse files- README.md +3 -4
- app.py +243 -0
- requirements.txt +4 -0
README.md
CHANGED
@@ -1,13 +1,12 @@
|
|
1 |
---
|
2 |
title: AIGenDeck
|
3 |
-
emoji:
|
4 |
-
colorFrom:
|
5 |
-
colorTo:
|
6 |
sdk: gradio
|
7 |
sdk_version: 3.36.1
|
8 |
app_file: app.py
|
9 |
pinned: false
|
10 |
-
license: mit
|
11 |
---
|
12 |
|
13 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
|
|
1 |
---
|
2 |
title: AIGenDeck
|
3 |
+
emoji: 💻
|
4 |
+
colorFrom: indigo
|
5 |
+
colorTo: pink
|
6 |
sdk: gradio
|
7 |
sdk_version: 3.36.1
|
8 |
app_file: app.py
|
9 |
pinned: false
|
|
|
10 |
---
|
11 |
|
12 |
Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
|
app.py
ADDED
@@ -0,0 +1,243 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import gradio as gr
|
2 |
+
from gradio.inputs import File as InputFile
|
3 |
+
from gradio.outputs import File as OutputFile
|
4 |
+
import base64
|
5 |
+
import glob
|
6 |
+
import os
|
7 |
+
import random
|
8 |
+
import re
|
9 |
+
import string
|
10 |
+
from urllib.parse import urlparse
|
11 |
+
|
12 |
+
import openai
|
13 |
+
from icrawler import ImageDownloader
|
14 |
+
from icrawler.builtin import GoogleImageCrawler
|
15 |
+
from pptx import Presentation
|
16 |
+
|
17 |
+
|
18 |
+
global unique_image_name
|
19 |
+
global gptmodel
|
20 |
+
unique_image_name = None
|
21 |
+
|
22 |
+
unique_image_name = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in
|
23 |
+
range(16))
|
24 |
+
|
25 |
+
def refresh_unique_image_name():
|
26 |
+
global unique_image_name
|
27 |
+
unique_image_name = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits)
|
28 |
+
for _ in range(16))
|
29 |
+
return
|
30 |
+
|
31 |
+
class PrefixNameDownloader(ImageDownloader):
|
32 |
+
|
33 |
+
# def get_filename(self, task, default_ext):
|
34 |
+
# filename = super(PrefixNameDownloader, self).get_filename(
|
35 |
+
# task, default_ext)
|
36 |
+
# refresh_unique_image_name()
|
37 |
+
# print(unique_image_name)
|
38 |
+
# return 'prefix_' + unique_image_name + filename
|
39 |
+
|
40 |
+
def get_filename(self, task, default_ext):
|
41 |
+
url_path = urlparse(task['file_url'])[2]
|
42 |
+
if '.' in url_path:
|
43 |
+
extension = url_path.split('.')[-1]
|
44 |
+
if extension.lower() not in [
|
45 |
+
'jpg', 'jpeg', 'png', 'bmp', 'tiff', 'gif', 'ppm', 'pgm'
|
46 |
+
]:
|
47 |
+
extension = default_ext
|
48 |
+
else:
|
49 |
+
extension = default_ext
|
50 |
+
print(unique_image_name)
|
51 |
+
filename = base64.b64encode(url_path.encode()).decode()
|
52 |
+
return "p_" + unique_image_name + '{}.{}'.format(filename, extension)
|
53 |
+
|
54 |
+
def generate_ppt(apikey, topic, slide_length, graph_presence, gptmodelname, templatepptx):
|
55 |
+
|
56 |
+
gptmodel = gptmodelname
|
57 |
+
|
58 |
+
root = Presentation(templatepptx.name)
|
59 |
+
|
60 |
+
openai.api_key = apikey
|
61 |
+
|
62 |
+
strGrpahics = "no"
|
63 |
+
|
64 |
+
if graph_presence == "Few":
|
65 |
+
strGrpahics = "few"
|
66 |
+
if graph_presence == "Many":
|
67 |
+
strGrpahics = "many"
|
68 |
+
|
69 |
+
message = f"""Create an outline for a slideshow presentation on the topic of {topic} which is {slide_length}
|
70 |
+
slides long. Make sure it is {slide_length} long.
|
71 |
+
Create {strGrpahics} images.
|
72 |
+
You are allowed to use the following slide types:
|
73 |
+
Title Slide - (Title, Subtitle)
|
74 |
+
Content Slide - (Title, Content)
|
75 |
+
Image Slide - (Title, Content, Image)
|
76 |
+
Thanks Slide - (Title)
|
77 |
+
Put this tag before the Title Slide: [L_TS]
|
78 |
+
Put this tag before the Content Slide: [L_CS]
|
79 |
+
Put this tag before the Image Slide: [L_IS]
|
80 |
+
Put this tag before the Thanks Slide: [L_THS]
|
81 |
+
|
82 |
+
Put this tag before the Title: [TITLE]
|
83 |
+
Put this tag after the Title: [/TITLE]
|
84 |
+
Put this tag before the Subitle: [SUBTITLE]
|
85 |
+
Put this tag after the Subtitle: [/SUBTITLE]
|
86 |
+
Put this tag before the Content: [CONTENT]
|
87 |
+
Put this tag after the Content: [/CONTENT]
|
88 |
+
Put this tag before the Image: [IMAGE]
|
89 |
+
Put this tag after the Image: [/IMAGE]
|
90 |
+
Put "[SLIDEBREAK]" after each slide
|
91 |
+
For example:
|
92 |
+
[L_TS]
|
93 |
+
[TITLE]Among Us[/TITLE]
|
94 |
+
[SLIDEBREAK]
|
95 |
+
[L_CS]
|
96 |
+
[TITLE]What Is Among Us?[/TITLE]
|
97 |
+
[CONTENT]
|
98 |
+
1. Among Us is a popular online multiplayer game developed and published by InnerSloth.
|
99 |
+
2. The game is set in a space-themed setting where players take on the roles of Crewmates and Impostors.
|
100 |
+
3. The objective of Crewmates is to complete tasks and identify the Impostors among them, while the Impostors' goal is to sabotage the spaceship and eliminate the Crewmates without being caught.
|
101 |
+
[/CONTENT]
|
102 |
+
[SLIDEBREAK]
|
103 |
+
Elaborate on the Content, provide as much information as possible.
|
104 |
+
REMEMBER TO PLACE a [/CONTENT] at the end of the Content.
|
105 |
+
Do not include any special characters (?, !, ., :, ) in the Title.
|
106 |
+
Do not include any additional information in your response and stick to the format."""
|
107 |
+
|
108 |
+
response = openai.ChatCompletion.create(
|
109 |
+
model=gptmodel,
|
110 |
+
messages=[
|
111 |
+
{"role": "user", "content": message}
|
112 |
+
]
|
113 |
+
)
|
114 |
+
|
115 |
+
# """ Ref for slide types:
|
116 |
+
# 0 -> title and subtitle
|
117 |
+
# 1 -> title and content
|
118 |
+
# 2 -> section header
|
119 |
+
# 3 -> two content
|
120 |
+
# 4 -> Comparison
|
121 |
+
# 5 -> Title only
|
122 |
+
# 6 -> Blank
|
123 |
+
# 7 -> Content with caption
|
124 |
+
# 8 -> Pic with caption
|
125 |
+
# """
|
126 |
+
|
127 |
+
def delete_all_slides():
|
128 |
+
for i in range(len(root.slides) - 1, -1, -1):
|
129 |
+
r_id = root.slides._sldIdLst[i].rId
|
130 |
+
root.part.drop_rel(r_id)
|
131 |
+
del root.slides._sldIdLst[i]
|
132 |
+
|
133 |
+
def create_title_slide(title, subtitle):
|
134 |
+
layout = root.slide_layouts[0]
|
135 |
+
slide = root.slides.add_slide(layout)
|
136 |
+
slide.shapes.title.text = title
|
137 |
+
slide.placeholders[1].text = subtitle
|
138 |
+
|
139 |
+
def create_section_header_slide(title):
|
140 |
+
layout = root.slide_layouts[2]
|
141 |
+
slide = root.slides.add_slide(layout)
|
142 |
+
slide.shapes.title.text = title
|
143 |
+
|
144 |
+
def create_title_and_content_slide(title, content):
|
145 |
+
layout = root.slide_layouts[1]
|
146 |
+
slide = root.slides.add_slide(layout)
|
147 |
+
slide.shapes.title.text = title
|
148 |
+
slide.placeholders[1].text = content
|
149 |
+
|
150 |
+
def create_title_and_content_and_image_slide(title, content, image_query):
|
151 |
+
layout = root.slide_layouts[8]
|
152 |
+
slide = root.slides.add_slide(layout)
|
153 |
+
slide.shapes.title.text = title
|
154 |
+
slide.placeholders[2].text = content
|
155 |
+
refresh_unique_image_name()
|
156 |
+
google_crawler = GoogleImageCrawler(downloader_cls=PrefixNameDownloader, storage={'root_dir': os.getcwd()})
|
157 |
+
google_crawler.crawl(keyword=image_query, max_num=1)
|
158 |
+
dir_path = os.path.dirname(os.path.realpath(__file__))
|
159 |
+
file_name = glob.glob(f"p_{unique_image_name}*")
|
160 |
+
print(file_name)
|
161 |
+
img_path = os.path.join(dir_path, file_name[0])
|
162 |
+
slide.shapes.add_picture(img_path, slide.placeholders[1].left, slide.placeholders[1].top,
|
163 |
+
slide.placeholders[1].width, slide.placeholders[1].height)
|
164 |
+
|
165 |
+
def find_text_in_between_tags(text, start_tag, end_tag):
|
166 |
+
start_pos = text.find(start_tag)
|
167 |
+
end_pos = text.find(end_tag)
|
168 |
+
result = []
|
169 |
+
while start_pos > -1 and end_pos > -1:
|
170 |
+
text_between_tags = text[start_pos + len(start_tag):end_pos]
|
171 |
+
result.append(text_between_tags)
|
172 |
+
start_pos = text.find(start_tag, end_pos + len(end_tag))
|
173 |
+
end_pos = text.find(end_tag, start_pos)
|
174 |
+
res1 = "".join(result)
|
175 |
+
res2 = re.sub(r"\[IMAGE\].*?\[/IMAGE\]", '', res1)
|
176 |
+
if len(result) > 0:
|
177 |
+
return res2
|
178 |
+
else:
|
179 |
+
return ""
|
180 |
+
|
181 |
+
def search_for_slide_type(text):
|
182 |
+
tags = ["[L_TS]", "[L_CS]", "[L_IS]", "[L_THS]"]
|
183 |
+
found_text = next((s for s in tags if s in text), None)
|
184 |
+
return found_text
|
185 |
+
|
186 |
+
def parse_response(reply):
|
187 |
+
list_of_slides = reply.split("[SLIDEBREAK]")
|
188 |
+
for slide in list_of_slides:
|
189 |
+
slide_type = search_for_slide_type(slide)
|
190 |
+
if slide_type == "[L_TS]":
|
191 |
+
create_title_slide(find_text_in_between_tags(str(slide), "[TITLE]", "[/TITLE]"),
|
192 |
+
find_text_in_between_tags(str(slide), "[SUBTITLE]", "[/SUBTITLE]"))
|
193 |
+
elif slide_type == "[L_CS]":
|
194 |
+
create_title_and_content_slide(
|
195 |
+
"".join(find_text_in_between_tags(str(slide), "[TITLE]", "[/TITLE]")),
|
196 |
+
"".join(find_text_in_between_tags(str(slide), "[CONTENT]",
|
197 |
+
"[/CONTENT]")))
|
198 |
+
elif slide_type == "[L_IS]":
|
199 |
+
create_title_and_content_and_image_slide("".join(find_text_in_between_tags(str(slide), "[TITLE]",
|
200 |
+
"[/TITLE]")),
|
201 |
+
"".join(find_text_in_between_tags(str(slide), "[CONTENT]",
|
202 |
+
"[/CONTENT]")),
|
203 |
+
"".join(find_text_in_between_tags(str(slide), "[IMAGE]",
|
204 |
+
"[/IMAGE]")))
|
205 |
+
elif slide_type == "[L_THS]":
|
206 |
+
create_section_header_slide("".join(find_text_in_between_tags(str(slide), "[TITLE]", "[/TITLE]")))
|
207 |
+
|
208 |
+
def find_title():
|
209 |
+
return root.slides[0].shapes.title.text
|
210 |
+
|
211 |
+
delete_all_slides()
|
212 |
+
|
213 |
+
print(response)
|
214 |
+
|
215 |
+
parse_response(response['choices'][0]['message']['content'])
|
216 |
+
|
217 |
+
root.save(f"{find_title()}.pptx")
|
218 |
+
|
219 |
+
print("done")
|
220 |
+
|
221 |
+
output_pptx = find_title() + ".pptx"
|
222 |
+
|
223 |
+
return output_pptx
|
224 |
+
|
225 |
+
|
226 |
+
iface = gr.Interface(
|
227 |
+
fn=generate_ppt,
|
228 |
+
inputs=[
|
229 |
+
gr.Textbox(label='OpenAI API Key', type='password'),
|
230 |
+
gr.Textbox(label='Topic'),
|
231 |
+
gr.Number(label='Number of slides', value=5, minimum=5, maximum=20),
|
232 |
+
gr.Radio(['None', 'Few', 'Many'], label='Graphical presence', value='Many'),
|
233 |
+
gr.Radio(['gpt-3.5-turbo', 'gpt-4'], label='GPT Model', value='gpt-4'),
|
234 |
+
InputFile(label="Upload PPTX template (blank .pptx file)"),
|
235 |
+
],
|
236 |
+
outputs=[
|
237 |
+
OutputFile(label="Download PPTX")
|
238 |
+
],
|
239 |
+
title="AIDeckGen",
|
240 |
+
description="Enter your OpenAI API Key, Presentation Topic, Number of slides, Graphical presence and upload a PPTX template to generate a presentation."
|
241 |
+
)
|
242 |
+
|
243 |
+
iface.launch()
|
requirements.txt
ADDED
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
1 |
+
openai
|
2 |
+
icrawler
|
3 |
+
python-pptx
|
4 |
+
beautifulsoup4
|