0% found this document useful (0 votes)
3 views

medium_com

Uploaded by

asadi knight
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
3 views

medium_com

Uploaded by

asadi knight
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 2

Search Write Sign up Sign in

Making An AI-Generated Book


Project / Start-Up
Try it out at aiebooks.xyz!

Nathan Ang · Follow


9 min read · Mar 8, 2024

-- 2

Intro
I’ve been making projects for a while on YouTube. In August 2023, I
wanted to make a bigger project. By ‘bigger’ I mean over 1 year.

So, I started ideating with my friend Jeremy. We had a couple ideas, one
was a new ticketing system for fans and another was a personal AI agent to
extract purchasable products from content you watch.

These seemed a bit too big scoped for me and my partner, so we decided to
make a platform to make AI-Generated E-Books, after seeing a rise in
people making money by making and selling these types of E-Books online.

Problem Statement / Current State


A lot of people are generating AI E-Books and selling them on apps like
Pinterest and Facebook. The target audience is middle aged women, on
topics like How To Lose Weight, How To Eat Healthy, and How To Save A
Failing Marriage.

However, their process is extremely manual — it takes 4–5 hours of effort


to make each book. We can automate this with code. The idea is that we
can make books to sell and make money, and also be a platform that others
pay a low fee to generate books.

Generating E-Books Programmatically


We can use the OpenAI API for this. It’s pretty much just making calls to
ChatGPT.

The API was a bit clunky, so I made a wrapper around it:

import uuid
import openai
import os
import requests

class GptWrapper:
def __init__(self):
self.OPENAI_API_KEY = os.environ.get("OPENAI_API_KEY")
openai.OPENAI_API_KEY = self.OPENAI_API_KEY
self.CHAT_GPT_MODELS = [
"gpt-3.5-turbo-1106",
"gpt-3.5-turbo",
]
self.GPT_MODEL_INDEX = 0
self.CHAT_GPT_MODEL = self.CHAT_GPT_MODELS[self.GPT_MODEL_INDEX]
self.convos = dict()

def retry(func):
def wrapper(self, *args, **kwargs):
while self.GPT_MODEL_INDEX < len(self.CHAT_GPT_MODELS) - 1:
try:
result = func(self, *args, **kwargs)
return result
except Exception as e:
# We should make this specific to the model error code
print(f"An exception occurred: {e}")
print(
f"Switching to the next model in self.CHAT_GPT_MODELS"
)
self.GPT_MODEL_INDEX += 1
self.CHAT_GPT_MODEL = self.CHAT_GPT_MODELS[
self.GPT_MODEL_INDEX
]
else:
raise Exception(
f"All models in self.CHAT_GPT_MODELS have been tried and"
f" exceeded"
)

return wrapper

@retry
def start_convo(self, system):
convo_id = str(uuid.uuid4())
self.convos[convo_id] = [
{"role": "system", "content": system},
]
return convo_id

@retry
def msg_in_convo(self, convo_id, prompt):
self.convos[convo_id].append({"role": "user", "content": prompt})
response = openai.ChatCompletion.create(
model=self.CHAT_GPT_MODEL,
messages=self.convos[convo_id],
)
text = response.choices[0].message.content.strip()
self.convos[convo_id].append({"role": "assistant", "content": text})
return text

@retry
def msg_in_convo_given_history(self, convo_id, messages):
self.convos[convo_id] = messages
response = openai.ChatCompletion.create(
model=self.CHAT_GPT_MODEL,
messages=messages,
)
text = response.choices[0].message.content.strip()
self.convos[convo_id].append({"role": "assistant", "content": text})
return text

@retry
def ask_question_in_convo(self, convo_id, question):
question += "Answer with only a single word: 'True' or 'False'"
self.convos[convo_id].append({"role": "user", "content": question})
response = openai.ChatCompletion.create(
model=self.CHAT_GPT_MODEL,
messages=self.convos[convo_id],
)
text = response.choices[0].message.content.strip()
response = True
if text.lower() == "true":
response = True
elif text.lower() == "false":
response = False
else:
raise Exception("Response is not a bool")
self.convos[convo_id].append({"role": "assistant", "content": text})
return response

@retry
def generate_photo(self, photo_prompt):
improved_gpt_prompt = (
f"A positive image of: {photo_prompt}, rendered artistically in a"
" chic, cartooney, minimalistic style."
)
response = openai.Image.create(
model="dall-e-3",
prompt=improved_gpt_prompt,
size="1024x1024",
quality="standard",
n=1,
)
image_url = response.data[0].url
img_data = requests.get(image_url).content
return img_data

Then, we just need to:

1. Generate Title

2. Generate Outline for the Book

3. Save Outline As A Data Structure (Map)

4. Loop Through Map

5. Format it as a Docx

6. Create A Cover

7. Turn it in to a PDF

8. Make some money!

Let’s Do It:

Generate Title

def generate_title(
self,
topic,
target_audience,
):
convo_id = self.GPT_WRAPPER.start_convo(GptSystems.AUTHOR_SYSTEM)
# Generate title
title_prompt = (
f'We are writing an eBook. It is about "{topic}". Our'
f' reader is: "{target_audience}". Write a short, catch'
" title clearly directed at our reader that is less than"
" 9 words and proposes a “big promise” that will be sure to grab"
" the readers attention."
)
title = self.GPT_WRAPPER.msg_in_convo(convo_id, title_prompt)
# remove surrounding quotes from title (if any)
title = title.replace('"', "")
return title

Generate Outline for the Book / Save Outline As A Data Structure (Map)

"""
Verify that ChatGPT is producing an outline in appropriate
"""

def verify_outline(self, outline, num_chapters, num_subsections):


# Check number of chapters
if len(outline.items()) != num_chapters:
return False
for chapter, subtopics in outline.items():
# Check that chapter includes chapter subject
if len(chapter) < 15:
return False
# Check that subtopics are in a list
if type(subtopics) != list:
return False
# Check number of subsections in each chapter
if len(subtopics) != num_subsections:
return False
return True

@retry(max_retries=3)
def generate_outline(
self,
topic,
target_audience,
title,
num_chapters,
num_subsections,
):
convo_id = self.GPT_WRAPPER.start_convo(GptSystems.AUTHOR_SYSTEM)
outline_prompt = (
f'We are writing an eBook called "{title}". It is about'
f' "{topic}". Our reader is: "{target_audience}". Create'
" a compehensive outline for our ebook, which will have"
f" {num_chapters} chapter(s). Each chapter should have exactly"
f" {num_subsections} subsection(s) Output Format for prompt:"
" python dict with key: chapter title, value: a single list/array"
" containing subsection titles within the chapter (the subtopics"
" should be inside the list). The chapter titles should be"
' prepended with the chapter number, like this: "Chapter 5:'
' [chapter title]". The subsection titles should be prepended'
' with the {chapter number.subtitle number}, like this: "5.4:'
' [subsection title]". '
)
outline_json = self.GPT_WRAPPER.msg_in_convo(convo_id, outline_prompt)
outline_json = outline_json[
outline_json.find("{") : outline_json.rfind("}") + 1
]
outline = json.loads(outline_json)
if not self.verify_outline(outline, num_chapters, num_subsections):
raise Exception("Outline not well formed!")

return outline

One interesting this is that we are using AI in-line to generate a data


structure we can use IN the code. This eliminates the need for manual
effort.

Output Format for prompt:"


" python dict with key: chapter title, value: a single list/array"
" containing subsection titles within the chapter (the subtopics"
" should be inside the list).

...

outline = json.loads(outline_json)

Loop To Generate Content

@retry(max_retries=1)
def generate_chapter_content(
self, title, topic, target_audience, idx, chapter, subtopic
):
convo_id = self.GPT_WRAPPER.start_convo(GptSystems.AUTHOR_SYSTEM)
num_words_str = "500 to 700"
content_prompt = (
f'We are writing an eBook called "{title}". Overall, it is about'
f' "{topic}". Our reader is: "{target_audience}". We are'
f" currently writing the #{idx+1} section for the chapter:"
f' "{chapter}". Using at least {num_words_str} words, write the'
" full contents of the section regarding this subtopic:"
f' "{subtopic}". The output should be as helpful to the reader as'
" possible. Include quantitative facts and statistics, with"
" references. Go as in depth as necessary. You can split this"
" into multiple paragraphs if you see fit. The output should also"
' be in cohesive paragraph form. Do not include any "[Insert'
' ___]" parts that will require manual editing in the book later.'
" If find yourself needing to put 'insert [blank]' anywhere, do"
" not do it (this is very important). If you do not know"
" something, do not include it in the output. Exclude any"
" auxillary information like the word count, as the entire"
" output will go directly into the ebook for readers, without any"
" human procesing. Remember the {num_words_str} word minimum,"
" please adhere to it."
)
content = self.GPT_WRAPPER.msg_in_convo(convo_id, content_prompt)

def remove_subtopic_from_content(content, subtopic, search_limit=200):


# Define the pattern to search for X.X where X is any number
pattern = r'\d\.\d'

# Search for the pattern in the first 100 characters of the input string
match = re.search(pattern, content[:search_limit])

if match:
# Find the index of the next newline character after the match
newline_index = content.find('\n', match.end())

if newline_index != -1:
# Extract the substring after the newline character
result_string = content[newline_index + 1:]
content = content.strip()
return result_string
return content

try:
content = content.strip()
content = remove_subtopic_from_content(content, subtopic)
except:
pass

print(content)
return content

Format it as a Docx
We can use the Python library docx.

@retry(max_retries=1)
def generate_docx(
self,
topic,
target_audience,
title,
outline,
docx_file,
book_template,
preview,
actionable_steps=False,
):
document = Document(book_template)
document.add_page_break() # Lets add a page and then remove it later. This
document.add_heading("Table of Contents")
for chapter, subtopics in outline.items():
document.add_heading(chapter, level=2)
for idx, subtopic in enumerate(subtopics):
document.add_heading("\t" + subtopic, level=3)

document.add_page_break()

chapter_num = 1
for chapter, subtopics in outline.items():
document.add_heading(chapter, level=1)
subtopics_content = []

# Generate each subtopic content


for idx, subtopic in enumerate(subtopics):
# Stop writing the ebook after four subsections if preview
if preview and idx >= 2:
break

document.add_heading(subtopic, level=2)

content = self.generate_chapter_content(
title, topic, target_audience, idx, chapter, subtopic
)
document.add_paragraph(content)
subtopics_content.append(content)

# Stop writing the ebook after one chapter if a preview


if preview:
document.add_heading(
"Preview Completed - Purchase Full Book To Read More!"
)
break

if chapter_num < len(outline.items()):


document.add_page_break()
chapter_num += 1

document.save(docx_file)

Generate a Book Cover

def generate_cover(
self, cover_template, title, topic, target_audience, output_file, preview
):
doc = DocxTemplate(cover_template)

if preview:
self.cover_photo_location = f"app/ai_book_generation/templates/covers/pr
else:
self.generate_cover_photo(
title, topic, target_audience, self.cover_photo_location
)
imagen = InlineImage(
doc, self.cover_photo_location, width=Mm(120)
) # width is in millimetres
context = {"title": title, "subtext": "NJ Publishing", "image": imagen}
doc.render(context)
doc.save(self.cover_location)
convert(self.cover_location, output_file)

def generate_cover_photo(self, title, topic, target_audience, img_output):


convo_id = self.GPT_WRAPPER.start_convo(GptSystems.AUTHOR_SYSTEM)
cover_prompt = (
f'We have a ebook with the title {title}. It is about "{topic}".'
f' Our reader is: "{target_audience}". Write me a very brief and'
" matter-of-fact description of a photo that would be on the"
" cover of the book. Do not reference the cover or photo in your"
' answer. For example, if the title was "How to lose weight for'
' middle aged women", a reasonable response would be "a middle'
' age woman exercising"'
)
print(cover_prompt)
dalle_prompt = self.GPT_WRAPPER.msg_in_convo(convo_id, cover_prompt)
print(dalle_prompt)
img_data = self.GPT_WRAPPER.generate_photo(dalle_prompt)
with open(img_output, "wb") as handler:
handler.write(img_data)

Turn it in to a PDF
Time to use Python library docx2pdf

convert(docx_file, self.content_pdf_location_temp)

Make some money!


We sold them with Facebook and Pinterest ads, and made some money!

Making A SAAS Platform


Okay, that’s not bad, but we still are bogged down by actually selling the
books ourselves and dealing with ads stuff. And as you may know, during
the gold rush, it wasn’t the miners making the real money, but rather it
was people selling the shovels.

So let’s make this into a SAAS (Software As A Service), so people who want
to make ebooks can do so on our platform.

We made a frontend for this using Next.js, which I won’t get into because I
don’t really do frontend stuff.

Then, we have code that we can run locally on our computer, but now we
want to run it on the Internet. To do this, we have to turn our backend
code into an API (Application Programming Interface).

There’s a ton of ways to do this (Django, Flask, FastAPI), but I think the
simplest and easist to use is Flask, so that’s what we’re going to go with.
Surprisingly, I was not able to find any templates that would help us with
this. I thought this was a pretty common use case. So, I made my own —
it’s here: https://github.com/nathan-149/flask-backend-api-
template/blob/main/README.md

The frontend simply then calls this new API with HTTP calls, and our
backend code runs as if it did locally!

There was a ton of stuff we had to implement, some of which were:

Preview Functionality On Website

Payment Flow

Fulfillment Flow (Storing and Emailing Completed Books To


Customers)

Parallelization Of Work

Virtualization (Running on AWS Windows Instance)

I also had an existential crisis that I didn’t want to cater only towards
these ‘hustlers’ so we pivoted to catering to people who wanted to actually
learn something. These E-Books were pretty good for this, and I generated
books about the History of The Rothschild Family and Quantum
Computing and they were actually really great.

Here’s some screenshots:

Home Page

Home Page For Hustlers!

Create E-Book Page

Cover Of A Generated Book

Random Page From The Generated Book

And yeah, we made some money:

Applying To YC
I applied to YC for this but ultimately got rejected. The Total Addressable
Market in the Public Education sector was not looking very great, so I
don’t blame them. All good though — I’ll try again for a startup (not just to
YC) with a different, better idea. :)

Conclusion
I’ll probably add to this at some point, but I hope you liked reading this.
Let me know!

You can checkout the full video here:

Ecommerce Web Development AI Programming ChatGPT

-- 2

Written by Nathan Ang Follow


601 Followers · 8 Following

gettin good — Carnegie Mellon ECE

Responses (2)

See all responses

Help Status About Careers Press Blog Privacy Terms Text to speech Teams

You might also like