For this project ,I decided to use my new go-to language Go (Golang).
You might wonder why i didn't use my main language, PHP. Well, I just wanted to try something new and have some fun experimenting.
To handle the AI part, I used the package github.com/openai/openai-go/v3 to make requests to OpenAIs API.
For PDF generation, I went with github.com/signintech/gopdf , Which is great for creating simple PDFs (nothing fancy in design just clean and functional).
And finally, I used github.com/joho/godotenv to load environment variables because yeah, Id rather not leak my API keys while testing with ChatGPT.
Since i don't have much time to manually create a PDF manual for every new product I add, I started thinking of ways to automate the process. Ive seen a lot of people use AI to speed up their work and in my case, its just a side project so an idea popped into my head:
Why not build a program that generates the manuals for me?
I brainstormed a bunch of approaches but decided to stick with something simple you know, KISS (Keep It Simple, Stupid).
The idea was straightforward: take a product name and build a prompt using some predefined, general-purpose questions.
Of course, before jumping into code, I had to figure out how real companies actually write their product manuals. So I did a bit of research (with some help from GPT, of course).
Once I had the structure product name + predefined questions it was time to start cooking. (Ive always wanted to say that!)
Let's start by getting product name using this function:
func GetProductName() string {
ofmt.Print("Enter product name: ")
odefer fmt.Println("Name was received")
ovar productName string
o_, err := fmt.Scan(&productName)
oif err != nil {
olog.Fatal(err)
o}
oreturn productName
}Then ,I created a Question struct to hold all the predefined questions:
type Question struct {
oID int
oQuestion string
oType string
}And a function to return all the questions:
func GetQuestions() []Question {
oreturn []Question{
o{ID: 1, Question: "What product is for", Type: "text"},
o{ID: 2, Question: "What feature this product has", Type: "list"},
o{ID: 3, Question: "How to setup this product", Type: "text"},
o{ID: 4, Question: "How to maintaine this product", Type: "text"},
o{ID: 5, Question: "How to use this product", Type: "text"},
o{ID: 6, Question: "What are the technical specifications", Type: "list"},
o{ID: 7, Question: "What materials is the product made from", Type: "text"},
o{ID: 8, Question: "What safety precautions should be followed", Type: "list"},
o{ID: 9, Question: "What warranty does the product have", Type: "text"},
o{ID: 10, Question: "What are common issues and troubleshooting steps", Type: "list"},
o{ID: 11, Question: "What accessories or spare parts are available", Type: "list"},
o{ID: 12, Question: "How to store the product when not in use", Type: "text"},
o{ID: 13, Question: "How long is the expected lifespan of the product", Type: "text"},
o{ID: 14, Question: "Is the product eco-friendly or recyclable", Type: "text"},
o{ID: 15, Question: "Who to contact for support or repairs", Type: "text"},
o}
}Next,I built a function to generate a prompt that I can send to OpenAI:
func gptPromptBuilder(productName string) string {
ovar builder strings.Builder
obuilder.WriteString(fmt.Sprintf("Generate answers for the following questions about %s.\n", productName))
obuilder.WriteString("Return your response **only** as a valid JSON object, no markdown, no explanations.\n")
obuilder.WriteString("Each key must be the exact question, and each value the detailed answer.\n\n")
obuilder.WriteString("Questions:\n")
ofor _, q := range GetQuestions() {
obuilder.WriteString(fmt.Sprintf("- %s?\n", q.Question))
o}
oreturn builder.String()
}Then ,I wrote a function to call the OpenAI API using my prompt:
func OpenAiClient(prompt string) (*responses.Response, error) {
oclient := openai.NewClient(option.WithAPIKey(os.Getenv("OPENAI_API_KEY")))
oparams := responses.ResponseNewParams{
oModel: openai.ChatModelGPT4oMini,
oTemperature: openai.Float(0.7),
oMaxOutputTokens: openai.Int(2000),
oInput: responses.ResponseNewParamsInputUnion{
oOfString: openai.String(prompt),
o},
o}
oresp, err := client.Responses.New(context.TODO(), params)
oif err != nil {
olog.Fatalf("Error calling OpenAI API: %v", err)
o}
oreturn resp, err
}Next ,I will use the return response func OpenAiClient to make a map with question as key and answers as value, So i can map over them and printing question and answer like in the following code:
myQuestionsAndAnswers := make(map[string]string)
if err := json.Unmarshal([]byte(strings.TrimSpace(response.OutputText())), &myQuestionsAndAnswers); err != nil {
log.Fatalf(" Failed to parse GPT JSON: %v\nResponse was:\n%s", err, strings.TrimSpace(response.OutputText()))
}I used gopdf to generate the PDF. First, initialize the instance and set the font:
pdf := gopdf.GoPdf{}
pdf.Start(gopdf.Config{PageSize: *gopdf.PageSizeA4})
fontPath := os.Getenv("FONT_PATH")
if _, err := os.Stat(fontPath); os.IsNotExist(err) {
olog.Fatalf("Font path does not exist: %s", fontPath)
}
pdf.AddTTFFont("ARIAL", fontPath)
pdf.SetFont("ARIAL", "", 12)I set some basic parameters for layout:
pageMargin := 40.0 lineHeight := 16.0 pageHeight := gopdf.PageSizeA4.H maxWidth := gopdf.PageSizeA4.W - 2*pageMargin
Then iterate over the questions and answers, creating pages as needed:
pdf.AddPage()
y := pageMargin
for _, q := range questions {
oanswer := myQuestionsAndAnswers[q.Question]
opdf.SetXY(pageMargin, y)
opdf.SetFont("ARIAL", "", 14)
opdf.Text(fmt.Sprintf("%s:", q.Question))
oy += lineHeight * 1.5
opdf.SetFont("ARIAL", "", 12)
olines := helpers.SplitTextToLines(&pdf, answer, maxWidth)
ofor _, line := range lines {
oif y+lineHeight > pageHeight-pageMargin {
opdf.AddPage()
oy = pageMargin
o}
opdf.SetXY(pageMargin, y)
opdf.Text(line)
oy += lineHeight
o}
oy += lineHeight
}Finally ,Save the PDF:
pdfName := fmt.Sprintf("%s_manual.pdf", productName)
pdf.WritePdf(pdfName)Now ,This was for me my first experienc making ai do something for me like .
The next step is to make this code available through my API so that a PDF manual can be generated automatically for any new product created. Ill cover that in another article.
Thank you!