Upload folder using huggingface_hub
Browse files- .gitignore +1 -0
- README.md +5 -7
- app.py +139 -0
- config/config.yaml +32 -0
- logs/app.log +3 -0
- newsletter_examples/0.txt +193 -0
- newsletter_examples/1.txt +186 -0
- newsletter_examples/2.txt +200 -0
- newsletter_examples/personalised_text_1.txt +131 -0
- personalised_text.py +139 -0
- prompt_templates/0.txt +18 -0
- prompt_templates/1.txt +38 -0
- prompt_templates/personalised_text_1.txt +28 -0
- requirements.txt +3 -0
- src/__pycache__/utils.cpython-311.pyc +0 -0
- src/__pycache__/utils.cpython-312.pyc +0 -0
- src/__pycache__/utils_api.cpython-311.pyc +0 -0
- src/__pycache__/utils_api.cpython-312.pyc +0 -0
- src/utils.py +169 -0
- src/utils_api.py +154 -0
.gitignore
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
test**
|
README.md
CHANGED
@@ -1,12 +1,10 @@
|
|
1 |
---
|
2 |
-
title:
|
3 |
-
|
4 |
-
colorFrom: purple
|
5 |
-
colorTo: gray
|
6 |
sdk: gradio
|
7 |
sdk_version: 5.6.0
|
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
|
|
|
1 |
---
|
2 |
+
title: smartrec
|
3 |
+
app_file: app.py
|
|
|
|
|
4 |
sdk: gradio
|
5 |
sdk_version: 5.6.0
|
|
|
|
|
6 |
---
|
7 |
+
# smartrec
|
8 |
+
|
9 |
+
AI-powered personalized newsletters.
|
10 |
|
|
app.py
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import openai
|
2 |
+
import gradio as gr
|
3 |
+
import json
|
4 |
+
from openai import OpenAI
|
5 |
+
from src.utils import LLMHandler, format_prompt
|
6 |
+
from src.utils_api import get_recommendations
|
7 |
+
import yaml
|
8 |
+
import logging
|
9 |
+
import time
|
10 |
+
import argparse
|
11 |
+
import os
|
12 |
+
import tempfile
|
13 |
+
|
14 |
+
|
15 |
+
# logging.basicConfig(filename='logs/app.log', encoding='utf-8', level=logging.DEBUG)
|
16 |
+
logging.basicConfig(level=logging.DEBUG)
|
17 |
+
|
18 |
+
|
19 |
+
def main():
|
20 |
+
|
21 |
+
# get arguments with argparse
|
22 |
+
parser = argparse.ArgumentParser(description='Newsletter Generator')
|
23 |
+
parser.add_argument('--config-file', type=str, default='./config/config.yaml', help='Path to the configuration file.')
|
24 |
+
args = parser.parse_args()
|
25 |
+
|
26 |
+
logging.info("Starting the Newsletter Generator app...")
|
27 |
+
|
28 |
+
# Load configuration from YAML file
|
29 |
+
logging.debug("Loading configuration from config.yaml...")
|
30 |
+
with open(args.config_file, "r") as file:
|
31 |
+
config = yaml.safe_load(file)
|
32 |
+
|
33 |
+
# set environment variables for the recommender API
|
34 |
+
os.environ["RECOMMENDER_URL"] = config['recommender_api']['base_url']
|
35 |
+
os.environ["RECOMMENDER_KEY"] = config['recommender_api']['key']
|
36 |
+
|
37 |
+
# LLM settings
|
38 |
+
os.environ["OPENAI_KEY"] = config['llm']['api_key']
|
39 |
+
llm_settings = config['llm']
|
40 |
+
|
41 |
+
newsletter_meta_info = config['newsletter']
|
42 |
+
|
43 |
+
logging.debug(f"Configuration loaded: {config}")
|
44 |
+
|
45 |
+
# Initialize the LLM handler
|
46 |
+
logging.debug("Initializing the LLM handler...")
|
47 |
+
llm_handler = LLMHandler(**llm_settings)
|
48 |
+
logging.debug(f"LLM handler initialized with the following settings: {config['llm']}")
|
49 |
+
|
50 |
+
|
51 |
+
# Define the function to generate the newsletter using the OpenAI API
|
52 |
+
def generate_newsletter(
|
53 |
+
customer_id,
|
54 |
+
model_name,
|
55 |
+
temperature,
|
56 |
+
max_tokens,
|
57 |
+
system_message,
|
58 |
+
newsletter_preferences
|
59 |
+
):
|
60 |
+
|
61 |
+
|
62 |
+
# get recommendations
|
63 |
+
logging.debug("Getting recommendations...")
|
64 |
+
customer_info, recommendations, transactions = get_recommendations(customer_id, max_recs=newsletter_meta_info['max_recommendations'], max_transactions=newsletter_meta_info['max_recents_items'])
|
65 |
+
logging.debug(f"Recommendations: {recommendations}")
|
66 |
+
|
67 |
+
logging.debug(f"Transactions: {transactions}")
|
68 |
+
|
69 |
+
# create the prompt
|
70 |
+
logging.debug("Formatting the prompt...")
|
71 |
+
prompt = format_prompt(
|
72 |
+
customer_info,
|
73 |
+
recommendations,
|
74 |
+
transactions,
|
75 |
+
newsletter_preferences,
|
76 |
+
newsletter_meta_info
|
77 |
+
)
|
78 |
+
|
79 |
+
# generate the newsletter text and images using llm_handler
|
80 |
+
logging.debug("Generating the newsletter text...")
|
81 |
+
newsletter_text = llm_handler.generate_text(
|
82 |
+
prompt,
|
83 |
+
model_name=model_name,
|
84 |
+
temperature=temperature,
|
85 |
+
max_tokens=max_tokens,
|
86 |
+
system_message=system_message
|
87 |
+
)
|
88 |
+
|
89 |
+
# Save HTML to a temporary file
|
90 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as temp_file:
|
91 |
+
temp_file.write(newsletter_text.encode("utf-8"))
|
92 |
+
temp_file_path = temp_file.name
|
93 |
+
|
94 |
+
|
95 |
+
return newsletter_text, temp_file_path
|
96 |
+
|
97 |
+
# Gradio interface for the app
|
98 |
+
logging.debug("Creating interface...")
|
99 |
+
with gr.Blocks() as demo:
|
100 |
+
gr.Markdown("### Newsletter Generator")
|
101 |
+
|
102 |
+
customer_id = gr.Textbox(label="Client ID", value="04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c")
|
103 |
+
newsletter_preferences = gr.Textbox(label="Newsletter preferences", placeholder="The newsletter should be catchy.")
|
104 |
+
|
105 |
+
# llm_preferences = gr.Textbox(label="LLM Preferences", placeholder="Enter LLM preferences.", visible=False)
|
106 |
+
# create an openable block for the llm preferences
|
107 |
+
with gr.Accordion("LLM Preferences", open=False):
|
108 |
+
model_name = gr.Dropdown(label="Model Name", choices=["gpt-3.5-turbo", "gpt-4-turbo"], value='gpt-3.5-turbo')
|
109 |
+
temperature = gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, step=0.05, value=llm_handler.default_temperature)
|
110 |
+
max_tokens = gr.Number(label="Max Tokens", value=llm_handler.default_max_tokens)
|
111 |
+
system_message = gr.Textbox(label="System Message", placeholder="Enter the system message or Leave Blank.", value=llm_handler.default_system_message)
|
112 |
+
|
113 |
+
generate_button = gr.Button("Generate Newsletter")
|
114 |
+
|
115 |
+
# create a button to open the newsletter in a new tab
|
116 |
+
download = gr.DownloadButton(label="Download Newsletter")
|
117 |
+
|
118 |
+
with gr.Accordion("Newsletter", open=False):
|
119 |
+
newsletter_output = gr.HTML(label="Generated Newsletter")
|
120 |
+
|
121 |
+
generate_button.click(
|
122 |
+
fn=generate_newsletter,
|
123 |
+
inputs=[
|
124 |
+
customer_id,
|
125 |
+
model_name,
|
126 |
+
temperature,
|
127 |
+
max_tokens,
|
128 |
+
system_message,
|
129 |
+
newsletter_preferences],
|
130 |
+
outputs=[newsletter_output, download]
|
131 |
+
)
|
132 |
+
|
133 |
+
demo.launch(
|
134 |
+
share=True,
|
135 |
+
)
|
136 |
+
|
137 |
+
|
138 |
+
if __name__ == "__main__":
|
139 |
+
main()
|
config/config.yaml
ADDED
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
# llm configs
|
3 |
+
llm:
|
4 |
+
api_key: "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"
|
5 |
+
model_name: "gpt-4o"
|
6 |
+
default_temperature: 0.8
|
7 |
+
default_max_tokens: 3000
|
8 |
+
default_system_message: null
|
9 |
+
|
10 |
+
|
11 |
+
recommender_api:
|
12 |
+
base_url: "https://urfjxlnyxlehjqlucvuu.functions.supabase.co"
|
13 |
+
key: "CFSekdHEW2EPasoqjWTuPpvVHYQukD3ootXhd3JVUXdpuEea5d"
|
14 |
+
|
15 |
+
# app frontend
|
16 |
+
app:
|
17 |
+
server_port: 7860
|
18 |
+
|
19 |
+
# templates for newsletters and prompts
|
20 |
+
newsletter:
|
21 |
+
newsletter_example_path: "./newsletter_examples/2.txt"
|
22 |
+
prompt_template_path: "./prompt_templates/1.txt"
|
23 |
+
brand_logo: "https://seeklogo.com/images/L/luisa-spagnoli-logo-EF482BEE89-seeklogo.com.png"
|
24 |
+
brand_name: "AI x Fashion"
|
25 |
+
max_recommendations: 4
|
26 |
+
max_recents_items: 2
|
27 |
+
|
28 |
+
#personalised_text_example_path: "./newsletter_examples/1.txt"
|
29 |
+
#personalised_text_prompt_template_path: "./prompt_templates/1.txt"
|
30 |
+
|
31 |
+
# logo luisa spagnoli https://seeklogo.com/images/L/luisa-spagnoli-logo-EF482BEE89-seeklogo.com.png
|
32 |
+
# logo h&m https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/H%26M-Logo.svg/709px-H%26M-Logo.svg.png
|
logs/app.log
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
INFO:root:Starting the Newsletter Generator app...
|
2 |
+
INFO:root:Loading configuration from config.yaml...
|
3 |
+
INFO:root:Initializing the LLM handler...
|
newsletter_examples/0.txt
ADDED
@@ -0,0 +1,193 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Your Personal Style Update</title>
|
7 |
+
<style>
|
8 |
+
/* Reset styles for email clients */
|
9 |
+
body {
|
10 |
+
margin: 0;
|
11 |
+
padding: 0;
|
12 |
+
width: 100%;
|
13 |
+
font-family: Arial, sans-serif;
|
14 |
+
line-height: 1.6;
|
15 |
+
color: #000000;
|
16 |
+
}
|
17 |
+
|
18 |
+
/* Container styles */
|
19 |
+
.container {
|
20 |
+
max-width: 600px;
|
21 |
+
margin: 0 auto;
|
22 |
+
padding: 20px;
|
23 |
+
background-color: #ffffff;
|
24 |
+
border: 1px solid #cccccc;
|
25 |
+
}
|
26 |
+
|
27 |
+
/* Header styles */
|
28 |
+
.header {
|
29 |
+
text-align: center;
|
30 |
+
padding: 20px 0;
|
31 |
+
border-bottom: 2px solid #000000;
|
32 |
+
}
|
33 |
+
|
34 |
+
.header img {
|
35 |
+
max-width: 150px;
|
36 |
+
height: auto;
|
37 |
+
}
|
38 |
+
|
39 |
+
/* Section styles */
|
40 |
+
.section {
|
41 |
+
margin: 30px 0;
|
42 |
+
}
|
43 |
+
|
44 |
+
/* Deal styles */
|
45 |
+
.deal-box {
|
46 |
+
background-color: #e6e6e6;
|
47 |
+
padding: 15px;
|
48 |
+
margin: 10px 0;
|
49 |
+
border-radius: 5px;
|
50 |
+
border: 2px solid #000000;
|
51 |
+
}
|
52 |
+
|
53 |
+
/* Product and bought item styles */
|
54 |
+
.product, .bought-item {
|
55 |
+
display: inline-block;
|
56 |
+
width: 45%;
|
57 |
+
margin: 10px;
|
58 |
+
text-align: center;
|
59 |
+
vertical-align: top;
|
60 |
+
border: 1px solid #cccccc;
|
61 |
+
padding: 10px;
|
62 |
+
}
|
63 |
+
|
64 |
+
.product img, .bought-item img {
|
65 |
+
width: 100%;
|
66 |
+
max-width: 200px;
|
67 |
+
max-height: 250px;
|
68 |
+
height: auto;
|
69 |
+
}
|
70 |
+
|
71 |
+
/* Button styles */
|
72 |
+
.button {
|
73 |
+
display: inline-block;
|
74 |
+
padding: 12px 25px;
|
75 |
+
background-color: #000000;
|
76 |
+
color: #ffffff;
|
77 |
+
text-decoration: none;
|
78 |
+
border-radius: 3px;
|
79 |
+
margin: 10px;
|
80 |
+
font-weight: bold;
|
81 |
+
}
|
82 |
+
|
83 |
+
/* Footer styles */
|
84 |
+
.footer {
|
85 |
+
text-align: center;
|
86 |
+
padding: 20px;
|
87 |
+
background-color: #e6e6e6;
|
88 |
+
margin-top: 30px;
|
89 |
+
border-top: 2px solid #000000;
|
90 |
+
}
|
91 |
+
|
92 |
+
/* Enhanced text styles */
|
93 |
+
h1, h2, h3 {
|
94 |
+
color: #000000;
|
95 |
+
font-weight: bold;
|
96 |
+
}
|
97 |
+
|
98 |
+
/* Link styles */
|
99 |
+
a {
|
100 |
+
color: #0066cc;
|
101 |
+
text-decoration: underline;
|
102 |
+
}
|
103 |
+
|
104 |
+
/* Price style */
|
105 |
+
.price {
|
106 |
+
font-size: 1.2em;
|
107 |
+
font-weight: bold;
|
108 |
+
color: #cc0000;
|
109 |
+
}
|
110 |
+
</style>
|
111 |
+
</head>
|
112 |
+
<body>
|
113 |
+
<div class="container">
|
114 |
+
<!-- Header Section -->
|
115 |
+
<div class="header">
|
116 |
+
<img src="${brand_logo}" alt="Brand Logo">
|
117 |
+
</div>
|
118 |
+
|
119 |
+
<!-- Personal Greeting -->
|
120 |
+
<div class="section">
|
121 |
+
<h1>Hello [Customer_Name],</h1>
|
122 |
+
<p style="font-size: 16px;">We've curated some special picks just for you based on your style preferences.</p>
|
123 |
+
<p style="font-size: 16px;">[Personalized text based on preferences]</p>
|
124 |
+
</div>
|
125 |
+
|
126 |
+
<!-- Previously Bought Items Section -->
|
127 |
+
<div class="section">
|
128 |
+
<h2>Items You Recently Loved</h2>
|
129 |
+
<p style="font-size: 16px;">Here are a few items you've purchased recently. We thought you might like similar pieces!</p>
|
130 |
+
<div class="bought-item">
|
131 |
+
<img src="/api/placeholder/200/250" alt="Previous Product 1">
|
132 |
+
<h3>Casual Linen Shirt</h3>
|
133 |
+
<p class="price">$49.99</p>
|
134 |
+
<p style="font-size: 14px; color: #666666;">Perfect for casual outings, pair it with your favorite jeans!</p>
|
135 |
+
</div>
|
136 |
+
<div class="bought-item">
|
137 |
+
<img src="/api/placeholder/200/250" alt="Previous Product 2">
|
138 |
+
<h3>Classic Denim Jacket</h3>
|
139 |
+
<p class="price">$89.99</p>
|
140 |
+
<p style="font-size: 14px; color: #666666;">A staple in every wardrobe, layer it over a T-shirt or dress.</p>
|
141 |
+
</div>
|
142 |
+
</div>
|
143 |
+
|
144 |
+
<!-- Deals Section -->
|
145 |
+
<div class="section">
|
146 |
+
<h2 style="color: #cc0000;">This Week's Exclusive Deals</h2>
|
147 |
+
<div class="deal-box">
|
148 |
+
<h3>🌟 30% OFF ALL DRESSES</h3>
|
149 |
+
<p style="font-weight: bold;">Use code: SUMMER30</p>
|
150 |
+
</div>
|
151 |
+
<div class="deal-box">
|
152 |
+
<h3>👠 BUY 2 GET 1 FREE</h3>
|
153 |
+
<p style="font-weight: bold;">On all accessories</p>
|
154 |
+
</div>
|
155 |
+
</div>
|
156 |
+
|
157 |
+
<!-- Recommended Items -->
|
158 |
+
<div class="section">
|
159 |
+
<h2>Picked Just For You</h2>
|
160 |
+
<div class="product">
|
161 |
+
<img src="/api/placeholder/200/250" alt="Product 1">
|
162 |
+
<h3>Silk Midi Dress</h3>
|
163 |
+
<p class="price">$129.99</p>
|
164 |
+
<a href="#" class="button">Shop Now</a>
|
165 |
+
</div>
|
166 |
+
<div class="product">
|
167 |
+
<img src="/api/placeholder/200/250" alt="Product 2">
|
168 |
+
<h3>Classic Blazer</h3>
|
169 |
+
<p class="price">$189.99</p>
|
170 |
+
<a href="#" class="button">Shop Now</a>
|
171 |
+
</div>
|
172 |
+
</div>
|
173 |
+
|
174 |
+
<!-- Call to Action -->
|
175 |
+
<div class="section" style="text-align: center;">
|
176 |
+
<h2>Ready to Refresh Your Wardrobe?</h2>
|
177 |
+
<a href="#" class="button">View All New Arrivals</a>
|
178 |
+
<a href="#" class="button" style="background-color: #006600;">Book Styling Session</a>
|
179 |
+
</div>
|
180 |
+
|
181 |
+
<!-- Footer -->
|
182 |
+
<div class="footer">
|
183 |
+
<p style="font-weight: bold;">Stay stylish,<br>The ${brand_name} Team</p>
|
184 |
+
<p>
|
185 |
+
<small style="color: #000000;">
|
186 |
+
You received this email because you subscribed to our newsletter.
|
187 |
+
<a href="#">Unsubscribe</a> | <a href="#">View in browser</a>
|
188 |
+
</small>
|
189 |
+
</p>
|
190 |
+
</div>
|
191 |
+
</div>
|
192 |
+
</body>
|
193 |
+
</html>
|
newsletter_examples/1.txt
ADDED
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Your Personal Style Update</title>
|
7 |
+
<style>
|
8 |
+
/* Reset styles for email clients */
|
9 |
+
body {
|
10 |
+
margin: 0;
|
11 |
+
padding: 0;
|
12 |
+
width: 100%;
|
13 |
+
font-family: Arial, sans-serif;
|
14 |
+
line-height: 1.6;
|
15 |
+
color: #000000;
|
16 |
+
}
|
17 |
+
|
18 |
+
/* Container styles */
|
19 |
+
.container {
|
20 |
+
max-width: 600px;
|
21 |
+
margin: 0 auto;
|
22 |
+
padding: 20px;
|
23 |
+
background-color: #ffffff;
|
24 |
+
border: 1px solid #cccccc;
|
25 |
+
}
|
26 |
+
|
27 |
+
/* Header styles */
|
28 |
+
.header {
|
29 |
+
text-align: center;
|
30 |
+
padding: 20px 0;
|
31 |
+
border-bottom: 2px solid #000000;
|
32 |
+
}
|
33 |
+
|
34 |
+
/* Section styles */
|
35 |
+
.section {
|
36 |
+
margin: 30px 0;
|
37 |
+
}
|
38 |
+
|
39 |
+
/* Deal styles */
|
40 |
+
.deal-box {
|
41 |
+
background-color: #e6e6e6;
|
42 |
+
padding: 15px;
|
43 |
+
margin: 10px 0;
|
44 |
+
border-radius: 5px;
|
45 |
+
border: 2px solid #000000;
|
46 |
+
}
|
47 |
+
|
48 |
+
/* Product styles */
|
49 |
+
.product, .bought-item {
|
50 |
+
display: inline-block;
|
51 |
+
width: 45%;
|
52 |
+
margin: 10px;
|
53 |
+
text-align: center;
|
54 |
+
vertical-align: top;
|
55 |
+
border: 1px solid #cccccc;
|
56 |
+
padding: 10px;
|
57 |
+
}
|
58 |
+
|
59 |
+
/* Button styles */
|
60 |
+
.button {
|
61 |
+
display: inline-block;
|
62 |
+
padding: 12px 25px;
|
63 |
+
background-color: #000000;
|
64 |
+
color: #ffffff;
|
65 |
+
text-decoration: none;
|
66 |
+
border-radius: 3px;
|
67 |
+
margin: 10px;
|
68 |
+
font-weight: bold;
|
69 |
+
}
|
70 |
+
|
71 |
+
/* Footer styles */
|
72 |
+
.footer {
|
73 |
+
text-align: center;
|
74 |
+
padding: 20px;
|
75 |
+
background-color: #e6e6e6;
|
76 |
+
margin-top: 30px;
|
77 |
+
border-top: 2px solid #000000;
|
78 |
+
}
|
79 |
+
|
80 |
+
/* Enhanced text styles */
|
81 |
+
h1, h2, h3 {
|
82 |
+
color: #000000;
|
83 |
+
font-weight: bold;
|
84 |
+
}
|
85 |
+
|
86 |
+
/* Link styles */
|
87 |
+
a {
|
88 |
+
color: #0066cc;
|
89 |
+
text-decoration: underline;
|
90 |
+
}
|
91 |
+
|
92 |
+
/* Price style */
|
93 |
+
.price {
|
94 |
+
font-size: 1.2em;
|
95 |
+
font-weight: bold;
|
96 |
+
color: #cc0000;
|
97 |
+
}
|
98 |
+
</style>
|
99 |
+
</head>
|
100 |
+
<body>
|
101 |
+
<div class="container">
|
102 |
+
<!-- Header Section -->
|
103 |
+
<div class="header">
|
104 |
+
<img src="${brand_logo}" alt="Brand Logo">
|
105 |
+
</div>
|
106 |
+
|
107 |
+
<!-- Personal Greeting -->
|
108 |
+
<div class="section">
|
109 |
+
<h1>Hello [Customer_Name],</h1>
|
110 |
+
<p style="font-size: 16px;"> It's Paolo again, your personal fashion assistant! How is everything going there? [PERSONALIZED TEXT BASED ON USER TASTE AND PREFERENCES] </p>
|
111 |
+
</div>
|
112 |
+
|
113 |
+
<!-- Previously Bought Items Section -->
|
114 |
+
<div class="section">
|
115 |
+
<p style="font-size: 16px;"> I hope you are enjoying your latest purchased items! You recently bought:</p>
|
116 |
+
<div class="bought-item">
|
117 |
+
<img src="/api/placeholder/200/250" alt="Previous Product 1">
|
118 |
+
<h3>Casual Linen Shirt</h3>
|
119 |
+
<p class="price">$49.99</p>
|
120 |
+
<p style="font-size: 14px; color: #666666;">Perfect for casual outings, pair it with your favorite jeans!</p>
|
121 |
+
</div>
|
122 |
+
<div class="bought-item">
|
123 |
+
<img src="/api/placeholder/200/250" alt="Previous Product 2">
|
124 |
+
<h3>Classic Denim Jacket</h3>
|
125 |
+
<p class="price">$89.99</p>
|
126 |
+
<p style="font-size: 14px; color: #666666;">A staple in every wardrobe, layer it over a T-shirt or dress.</p>
|
127 |
+
</div>
|
128 |
+
<!-- Add a third item if desired -->
|
129 |
+
</div>
|
130 |
+
|
131 |
+
|
132 |
+
<!-- Recommended Items -->
|
133 |
+
<div class="section">
|
134 |
+
<p style="font-size: 16px;"> I hope you are enjoying your latest purchased items! Based on those, I would recommend the following: </p>
|
135 |
+
|
136 |
+
<h2>Picked Just For You</h2>
|
137 |
+
<div class="product">
|
138 |
+
<img src="/api/placeholder/200/250" alt="Product 1">
|
139 |
+
<h3>Silk Midi Dress</h3>
|
140 |
+
<p class="price">$129.99</p>
|
141 |
+
<a href="#" class="button">Shop Now</a>
|
142 |
+
</div>
|
143 |
+
<div class="product">
|
144 |
+
<img src="/api/placeholder/200/250" alt="Product 2">
|
145 |
+
<h3>Classic Blazer</h3>
|
146 |
+
<p class="price">$189.99</p>
|
147 |
+
<a href="#" class="button">Shop Now</a>
|
148 |
+
</div>
|
149 |
+
</div>
|
150 |
+
|
151 |
+
|
152 |
+
<!-- Deals Section -->
|
153 |
+
<div class="section">
|
154 |
+
<h2 style="color: #cc0000;">Nothing you like here ?</h2>
|
155 |
+
<p style="font-size: 16px;"> Well, I always try our best and recommend what we think would suit you well, but hey, we all make mistakes, right? </p>
|
156 |
+
<p style="font-size: 16px;"> No worries though, I still have a lot of stuff for you! Take a look at these weekly deals! </p>
|
157 |
+
<div class="deal-box">
|
158 |
+
<h3>🌟 30% OFF ALL DRESSES</h3>
|
159 |
+
<p style="font-weight: bold;">Use code: SUMMER30</p>
|
160 |
+
</div>
|
161 |
+
<div class="deal-box">
|
162 |
+
<h3>👠 BUY 2 GET 1 FREE</h3>
|
163 |
+
<p style="font-weight: bold;">On all accessories</p>
|
164 |
+
</div>
|
165 |
+
</div>
|
166 |
+
|
167 |
+
<!-- Call to Action -->
|
168 |
+
<div class="section" style="text-align: center;">
|
169 |
+
<h2>Ready to Refresh Your Wardrobe?</h2>
|
170 |
+
<a href="#" class="button">View All New Arrivals</a>
|
171 |
+
<a href="#" class="button" style="background-color: #006600;">Book Styling Session</a>
|
172 |
+
</div>
|
173 |
+
|
174 |
+
<!-- Footer -->
|
175 |
+
<div class="footer">
|
176 |
+
<p style="font-weight: bold;">Stay stylish,<br>The ${brand_name} Team</p>
|
177 |
+
<p>
|
178 |
+
<small style="color: #000000;">
|
179 |
+
You received this email because you subscribed to our newsletter.
|
180 |
+
<a href="#">Unsubscribe</a> | <a href="#">View in browser</a>
|
181 |
+
</small>
|
182 |
+
</p>
|
183 |
+
</div>
|
184 |
+
</div>
|
185 |
+
</body>
|
186 |
+
</html>
|
newsletter_examples/2.txt
ADDED
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Your Personalized Style Curation</title>
|
7 |
+
<style>
|
8 |
+
/* Elegant, sophisticated color palette with light brown background */
|
9 |
+
body {
|
10 |
+
margin: 0;
|
11 |
+
padding: 0;
|
12 |
+
width: 100%;
|
13 |
+
font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, serif;
|
14 |
+
line-height: 1.6;
|
15 |
+
color: #4A4A4A;
|
16 |
+
background-color: #F0E6D2; /* Light warm brown background */
|
17 |
+
}
|
18 |
+
|
19 |
+
.container {
|
20 |
+
max-width: 600px;
|
21 |
+
margin: 0 auto;
|
22 |
+
padding: 30px;
|
23 |
+
background-color: #FFFFFF;
|
24 |
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
25 |
+
border: 1px solid #E0D3BA; /* Slight border to complement background */
|
26 |
+
}
|
27 |
+
|
28 |
+
.header {
|
29 |
+
text-align: center;
|
30 |
+
padding: 20px 0;
|
31 |
+
border-bottom: 1px solid #D4AD7A;
|
32 |
+
margin-bottom: 30px;
|
33 |
+
}
|
34 |
+
|
35 |
+
.header img {
|
36 |
+
max-width: 200px;
|
37 |
+
height: auto;
|
38 |
+
}
|
39 |
+
|
40 |
+
.section {
|
41 |
+
margin-bottom: 40px;
|
42 |
+
}
|
43 |
+
|
44 |
+
h1, h2, h3 {
|
45 |
+
font-family: 'Didot', serif;
|
46 |
+
color: #5D4037; /* Deep brown for headings */
|
47 |
+
margin-bottom: 15px;
|
48 |
+
}
|
49 |
+
|
50 |
+
.bought-item, .product {
|
51 |
+
display: inline-block;
|
52 |
+
width: 45%;
|
53 |
+
margin: 10px;
|
54 |
+
text-align: center;
|
55 |
+
vertical-align: top;
|
56 |
+
padding: 20px;
|
57 |
+
background-color: #FFF8E7; /* Very light cream background */
|
58 |
+
border-radius: 8px;
|
59 |
+
border: 1px solid #E0D3BA;
|
60 |
+
transition: transform 0.3s ease;
|
61 |
+
}
|
62 |
+
|
63 |
+
.bought-item:hover, .product:hover {
|
64 |
+
transform: scale(1.02);
|
65 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
66 |
+
}
|
67 |
+
|
68 |
+
.deal-box {
|
69 |
+
background-color: #F5E6D3; /* Soft light brown */
|
70 |
+
padding: 20px;
|
71 |
+
margin: 15px 0;
|
72 |
+
border-radius: 8px;
|
73 |
+
text-align: center;
|
74 |
+
border: 1px solid #D4AD7A;
|
75 |
+
}
|
76 |
+
|
77 |
+
.button {
|
78 |
+
display: inline-block;
|
79 |
+
padding: 12px 25px;
|
80 |
+
background-color: #8B6914; /* Rich brown */
|
81 |
+
color: #FFFFFF;
|
82 |
+
text-decoration: none;
|
83 |
+
border-radius: 5px;
|
84 |
+
font-weight: 600;
|
85 |
+
text-transform: uppercase;
|
86 |
+
letter-spacing: 1px;
|
87 |
+
transition: background-color 0.3s ease;
|
88 |
+
}
|
89 |
+
|
90 |
+
.button:hover {
|
91 |
+
background-color: #5D4037; /* Darker brown */
|
92 |
+
}
|
93 |
+
|
94 |
+
.footer {
|
95 |
+
text-align: center;
|
96 |
+
padding: 20px;
|
97 |
+
background-color: #F5E6D3; /* Soft light brown */
|
98 |
+
border-top: 1px solid #D4AD7A;
|
99 |
+
font-size: 0.9em;
|
100 |
+
}
|
101 |
+
|
102 |
+
.price {
|
103 |
+
font-size: 1.2em;
|
104 |
+
color: #8B6914; /* Rich brown */
|
105 |
+
font-weight: bold;
|
106 |
+
}
|
107 |
+
|
108 |
+
a {
|
109 |
+
color: #8B6914; /* Rich brown */
|
110 |
+
text-decoration: none;
|
111 |
+
}
|
112 |
+
|
113 |
+
a:hover {
|
114 |
+
text-decoration: underline;
|
115 |
+
}
|
116 |
+
</style>
|
117 |
+
</head>
|
118 |
+
<body>
|
119 |
+
<div class="container">
|
120 |
+
<!-- Header Section -->
|
121 |
+
<div class="header">
|
122 |
+
<img src="${brand_logo}" alt="Brand Logo">
|
123 |
+
</div>
|
124 |
+
|
125 |
+
<!-- Personal Greeting -->
|
126 |
+
<div class="section">
|
127 |
+
<h1>Hello [Customer_Name],</h1>
|
128 |
+
<p> I wanted to take a moment to highlight some of the great pieces you've recently added to your wardrobe. We've curated some items that I think you'll love looking back on - each one tells a bit of your style story.
|
129 |
+
Here's a fresh look at the items you brought home recently: [LONG PERSONALIZED TEXT BASED ON USER TASTE AND PREFERENCES] </p>
|
130 |
+
</div>
|
131 |
+
|
132 |
+
<!-- Previously Bought Items Section -->
|
133 |
+
<div class="section">
|
134 |
+
|
135 |
+
<div class="bought-item">
|
136 |
+
<img src=[IMAGE LINK] alt="Previous Product 1">
|
137 |
+
<h3>Casual Linen Shirt</h3>
|
138 |
+
<p class="price">$49.99</p>
|
139 |
+
<p>Perfect for refined casual moments, effortlessly paired with tailored pieces.</p>
|
140 |
+
</div>
|
141 |
+
<div class="bought-item">
|
142 |
+
<img src=[IMAGE LINK] alt="Previous Product 2">
|
143 |
+
<h3>Classic Denim Jacket</h3>
|
144 |
+
<p class="price">$89.99</p>
|
145 |
+
<p>A versatile staple that elevates any ensemble with understated sophistication.</p>
|
146 |
+
</div>
|
147 |
+
</div>
|
148 |
+
|
149 |
+
<!-- Recommended Items -->
|
150 |
+
<p style="font-size: 16px;"> I've been looking through your recent data and noticed some really interesting patterns in your recent fashion choices. I wanted to share some new recommendations based on your previous purchases that I think will continue to elevate your personal style. These suggestions aren't just random picks, but carefully curated pieces that connect with the aesthetic you've been developing. [PERSONALIZED TEXT TO RECOMMEND NEW ITEMS BASED ON PREVIOUSLY BOUGHT ONES] </p>
|
151 |
+
<div class="section">
|
152 |
+
<div class="product">
|
153 |
+
<img src=[IMAGE LINK] alt="Product 1">
|
154 |
+
<h3>Silk Midi Dress</h3>
|
155 |
+
<p class="price">$129.99</p>
|
156 |
+
<a href="#" class="button">Explore</a>
|
157 |
+
</div>
|
158 |
+
<div class="product">
|
159 |
+
<img src=[IMAGE LINK] alt="Product 2">
|
160 |
+
<h3>Classic Blazer</h3>
|
161 |
+
<p class="price">$189.99</p>
|
162 |
+
<a href="#" class="button">Discover</a>
|
163 |
+
</div>
|
164 |
+
</div>
|
165 |
+
|
166 |
+
<!-- Deals Section -->
|
167 |
+
<div class="section">
|
168 |
+
<h2 style="color: #8B6914;">Not Quite Your Style?</h2>
|
169 |
+
<p style="font-size: 16px;"> Well, I always try our best and recommend what we think would suit you well, but hey, we all make mistakes, right? </p>
|
170 |
+
<p style="font-size: 16px;"> No worries though, I still have a lot of stuff for you! Take a look at these weekly deals! </p>
|
171 |
+
<div class="deal-box">
|
172 |
+
<h3>🌟 30% OFF ALL DRESSES</h3>
|
173 |
+
<p>Exclusive Code: LSF30</p>
|
174 |
+
</div>
|
175 |
+
<div class="deal-box">
|
176 |
+
<h3>👠 BUY 2 GET 1 FREE</h3>
|
177 |
+
<p>On our curated accessories collection</p>
|
178 |
+
</div>
|
179 |
+
</div>
|
180 |
+
|
181 |
+
<!-- Call to Action -->
|
182 |
+
<div class="section" style="text-align: center;">
|
183 |
+
<h2>Ready to Refine Your Wardrobe?</h2>
|
184 |
+
<a href="#" class="button">View New Arrivals</a>
|
185 |
+
<a href="#" class="button" style="background-color: #5D4037;">Book Styling Consultation</a>
|
186 |
+
</div>
|
187 |
+
|
188 |
+
<!-- Footer -->
|
189 |
+
<div class="footer">
|
190 |
+
<p style="font-weight: bold;">The ${brand_name} Team</p>
|
191 |
+
<p>
|
192 |
+
<small>
|
193 |
+
You're receiving this curated selection because you're part of our stylish community.
|
194 |
+
<a href="#">Unsubscribe</a> | <a href="#">View in Browser</a>
|
195 |
+
</small>
|
196 |
+
</p>
|
197 |
+
</div>
|
198 |
+
</div>
|
199 |
+
</body>
|
200 |
+
</html>
|
newsletter_examples/personalised_text_1.txt
ADDED
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
+
<html lang="en">
|
3 |
+
<head>
|
4 |
+
<meta charset="UTF-8">
|
5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
+
<title>Style Update for Sophia</title>
|
7 |
+
<style>
|
8 |
+
body {
|
9 |
+
font-family: Arial, sans-serif;
|
10 |
+
margin: 0;
|
11 |
+
padding: 0;
|
12 |
+
background-color: #f9f9f9;
|
13 |
+
color: #333;
|
14 |
+
}
|
15 |
+
.container {
|
16 |
+
max-width: 600px;
|
17 |
+
margin: 20px auto;
|
18 |
+
background: #fff;
|
19 |
+
border-radius: 8px;
|
20 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
21 |
+
overflow: hidden;
|
22 |
+
}
|
23 |
+
.header {
|
24 |
+
background-color: #fddde6;
|
25 |
+
color: #d63384;
|
26 |
+
padding: 20px;
|
27 |
+
text-align: center;
|
28 |
+
}
|
29 |
+
.header h1 {
|
30 |
+
font-size: 24px;
|
31 |
+
margin: 0;
|
32 |
+
}
|
33 |
+
.body {
|
34 |
+
padding: 20px;
|
35 |
+
}
|
36 |
+
.body h2 {
|
37 |
+
font-size: 20px;
|
38 |
+
margin-bottom: 10px;
|
39 |
+
color: #d63384;
|
40 |
+
}
|
41 |
+
.highlight {
|
42 |
+
background-color: #ffeef2;
|
43 |
+
padding: 10px;
|
44 |
+
border-radius: 5px;
|
45 |
+
margin-bottom: 20px;
|
46 |
+
}
|
47 |
+
.highlight h3 {
|
48 |
+
margin: 0 0 10px;
|
49 |
+
color: #333;
|
50 |
+
}
|
51 |
+
.button {
|
52 |
+
display: inline-block;
|
53 |
+
background-color: #d63384;
|
54 |
+
color: #fff;
|
55 |
+
text-decoration: none;
|
56 |
+
padding: 10px 20px;
|
57 |
+
border-radius: 5px;
|
58 |
+
font-weight: bold;
|
59 |
+
text-align: center;
|
60 |
+
}
|
61 |
+
.footer {
|
62 |
+
text-align: center;
|
63 |
+
padding: 20px;
|
64 |
+
background-color: #f9f9f9;
|
65 |
+
font-size: 12px;
|
66 |
+
color: #777;
|
67 |
+
}
|
68 |
+
.footer a {
|
69 |
+
color: #d63384;
|
70 |
+
text-decoration: none;
|
71 |
+
}
|
72 |
+
@media (max-width: 600px) {
|
73 |
+
.container {
|
74 |
+
margin: 10px;
|
75 |
+
}
|
76 |
+
}
|
77 |
+
</style>
|
78 |
+
</head>
|
79 |
+
<body>
|
80 |
+
<div class="container">
|
81 |
+
<!-- Header Section -->
|
82 |
+
<div class="header">
|
83 |
+
<img src="${brand_logo}" alt="Brand Logo">
|
84 |
+
<h1>Style Update Just for You, Sophia! 💖</h1>
|
85 |
+
</div>
|
86 |
+
|
87 |
+
<!-- Body Section -->
|
88 |
+
<div class="body">
|
89 |
+
<h2>Hi Sophia, 🌸</h2>
|
90 |
+
<p>Your New York style just got a glow-up! Here’s what’s trending and totally YOU:</p>
|
91 |
+
|
92 |
+
<!-- Highlight Section -->
|
93 |
+
<div class="highlight">
|
94 |
+
<h3>✨ Trending Now:</h3>
|
95 |
+
<ul>
|
96 |
+
<li><b>Oversized Chic:</b> Pair your hoodie with chunky sneakers (we know it’s on your wishlist 😉).</li>
|
97 |
+
<li><b>Floral Fantasy:</b> Pastel pink and sage green are making waves—just like your boho maxi dress!</li>
|
98 |
+
</ul>
|
99 |
+
</div>
|
100 |
+
|
101 |
+
<!-- Recommendations Section -->
|
102 |
+
<h3>🛍️ This Week’s Picks for You:</h3>
|
103 |
+
<ul>
|
104 |
+
<li>🌸 <b>Dreamy Wide-Leg Jeans</b> (Pastel Pink Edition)</li>
|
105 |
+
<li>🌿 <b>Eco-Friendly Tote Bag</b> to match your sustainable vibe</li>
|
106 |
+
<li>🎀 <b>Vintage-Inspired Jacket</b> for the perfect streetwear look</li>
|
107 |
+
</ul>
|
108 |
+
|
109 |
+
<!-- Subscription Perks Section -->
|
110 |
+
<h3>💡 Your VIP Perks:</h3>
|
111 |
+
<p>
|
112 |
+
As a VIP, you get:
|
113 |
+
<ul>
|
114 |
+
<li>Early access to new collections</li>
|
115 |
+
<li>Exclusive discounts (hint: wide-leg jeans are on sale!)</li>
|
116 |
+
<li>Free style consultations tailored to your fav styles 💌</li>
|
117 |
+
</ul>
|
118 |
+
</p>
|
119 |
+
|
120 |
+
<!-- Call-to-Action -->
|
121 |
+
<a href="#" class="button">Shop Now</a>
|
122 |
+
</div>
|
123 |
+
|
124 |
+
<!-- Footer Section -->
|
125 |
+
<div class="footer">
|
126 |
+
<p>Thanks for being part of our style squad, Sophia! 💖</p>
|
127 |
+
<p><a href="#">Unsubscribe</a> | <a href="#">Update Preferences</a></p>
|
128 |
+
</div>
|
129 |
+
</div>
|
130 |
+
</body>
|
131 |
+
</html>
|
personalised_text.py
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import openai
|
2 |
+
import gradio as gr
|
3 |
+
import json
|
4 |
+
from openai import OpenAI
|
5 |
+
from src.utils import LLMHandler, format_prompt_personalsed_text
|
6 |
+
from src.utils_api import get_recommendations
|
7 |
+
import yaml
|
8 |
+
import logging
|
9 |
+
import time
|
10 |
+
import argparse
|
11 |
+
import os
|
12 |
+
import tempfile
|
13 |
+
|
14 |
+
|
15 |
+
# logging.basicConfig(filename='logs/app.log', encoding='utf-8', level=logging.DEBUG)
|
16 |
+
logging.basicConfig(level=logging.DEBUG)
|
17 |
+
|
18 |
+
|
19 |
+
def main():
|
20 |
+
|
21 |
+
# get arguments with argparse
|
22 |
+
parser = argparse.ArgumentParser(description='Newsletter Generator')
|
23 |
+
parser.add_argument('--config-file', type=str, default='./config/config.yaml', help='Path to the configuration file.')
|
24 |
+
args = parser.parse_args()
|
25 |
+
|
26 |
+
logging.info("Starting the Newsletter Generator app...")
|
27 |
+
|
28 |
+
# Load configuration from YAML file
|
29 |
+
logging.debug("Loading configuration from config.yaml...")
|
30 |
+
with open(args.config_file, "r") as file:
|
31 |
+
config = yaml.safe_load(file)
|
32 |
+
|
33 |
+
# set environment variables for the recommender API
|
34 |
+
os.environ["RECOMMENDER_URL"] = config['recommender_api']['base_url']
|
35 |
+
os.environ["RECOMMENDER_KEY"] = config['recommender_api']['key']
|
36 |
+
|
37 |
+
# LLM settings
|
38 |
+
os.environ["OPENAI_KEY"] = config['llm']['api_key']
|
39 |
+
llm_settings = config['llm']
|
40 |
+
|
41 |
+
newsletter_meta_info = config['newsletter']
|
42 |
+
|
43 |
+
logging.debug(f"Configuration loaded: {config}")
|
44 |
+
|
45 |
+
# Initialize the LLM handler
|
46 |
+
logging.debug("Initializing the LLM handler...")
|
47 |
+
llm_handler = LLMHandler(**llm_settings)
|
48 |
+
logging.debug(f"LLM handler initialized with the following settings: {config['llm']}")
|
49 |
+
|
50 |
+
|
51 |
+
# Define the function to generate the newsletter using the OpenAI API
|
52 |
+
def generate_newsletter(
|
53 |
+
customer_id,
|
54 |
+
model_name,
|
55 |
+
temperature,
|
56 |
+
max_tokens,
|
57 |
+
system_message,
|
58 |
+
newsletter_preferences
|
59 |
+
):
|
60 |
+
|
61 |
+
|
62 |
+
# get recommendations
|
63 |
+
logging.debug("Getting recommendations...")
|
64 |
+
customer_info, recommendations, transactions = get_recommendations(customer_id)
|
65 |
+
logging.debug(f"Recommendations: {recommendations}")
|
66 |
+
|
67 |
+
logging.debug(f"Transactions: {transactions}")
|
68 |
+
|
69 |
+
|
70 |
+
# create the prompt
|
71 |
+
logging.debug("Formatting the prompt...")
|
72 |
+
prompt = format_prompt_personalsed_text(
|
73 |
+
customer_info,
|
74 |
+
recommendations,
|
75 |
+
transactions,
|
76 |
+
newsletter_preferences,
|
77 |
+
newsletter_meta_info
|
78 |
+
)
|
79 |
+
|
80 |
+
# generate the newsletter text and images using llm_handler
|
81 |
+
logging.debug("Generating the newsletter text...")
|
82 |
+
newsletter_text = llm_handler.generate_text(
|
83 |
+
prompt,
|
84 |
+
model_name=model_name,
|
85 |
+
temperature=temperature,
|
86 |
+
max_tokens=max_tokens,
|
87 |
+
system_message=system_message
|
88 |
+
)
|
89 |
+
|
90 |
+
# Save HTML to a temporary file
|
91 |
+
with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as temp_file:
|
92 |
+
temp_file.write(newsletter_text.encode("utf-8"))
|
93 |
+
temp_file_path = temp_file.name
|
94 |
+
|
95 |
+
|
96 |
+
return newsletter_text, temp_file_path
|
97 |
+
|
98 |
+
# Gradio interface for the app
|
99 |
+
logging.debug("Creating interface...")
|
100 |
+
with gr.Blocks() as demo:
|
101 |
+
gr.Markdown("### Newsletter Generator")
|
102 |
+
|
103 |
+
customer_id = gr.Textbox(label="Client ID", value="04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c")
|
104 |
+
newsletter_preferences = gr.Textbox(label="Newsletter preferences", placeholder="The newsletter should be catchy.")
|
105 |
+
|
106 |
+
# llm_preferences = gr.Textbox(label="LLM Preferences", placeholder="Enter LLM preferences.", visible=False)
|
107 |
+
# create an openable block for the llm preferences
|
108 |
+
with gr.Accordion("LLM Preferences", open=False):
|
109 |
+
model_name = gr.Dropdown(label="Model Name", choices=["gpt-3.5-turbo", "gpt-4-turbo"], value='gpt-3.5-turbo')
|
110 |
+
temperature = gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, step=0.05, value=llm_handler.default_temperature)
|
111 |
+
max_tokens = gr.Number(label="Max Tokens", value=llm_handler.default_max_tokens)
|
112 |
+
system_message = gr.Textbox(label="System Message", placeholder="Enter the system message or Leave Blank.", value=llm_handler.default_system_message)
|
113 |
+
|
114 |
+
generate_button = gr.Button("Generate Newsletter")
|
115 |
+
|
116 |
+
# create a button to open the newsletter in a new tab
|
117 |
+
download = gr.DownloadButton(label="Download Newsletter")
|
118 |
+
|
119 |
+
with gr.Accordion("Newsletter", open=False):
|
120 |
+
newsletter_output = gr.HTML(label="Generated Newsletter", value=open(config["newsletter"]["personalised_text_example_path"]).read())
|
121 |
+
|
122 |
+
generate_button.click(
|
123 |
+
fn=generate_newsletter,
|
124 |
+
inputs=[
|
125 |
+
customer_id,
|
126 |
+
model_name,
|
127 |
+
temperature,
|
128 |
+
max_tokens,
|
129 |
+
system_message,
|
130 |
+
newsletter_preferences],
|
131 |
+
outputs=[newsletter_output, download]
|
132 |
+
)
|
133 |
+
|
134 |
+
demo.launch(
|
135 |
+
server_port=config['app']['server_port'],
|
136 |
+
)
|
137 |
+
|
138 |
+
if __name__ == "__main__":
|
139 |
+
main()
|
prompt_templates/0.txt
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
### Instructions:
|
3 |
+
Write the newsletter using the provided template, making it engaging and personalized for the client. Incorporate the recommended items naturally within the body of the newsletter, keeping a ${intonation} tone throughout. Adhere to the specified length and include relevant call-to-action phrases for each recommended item to increase client engagement.
|
4 |
+
|
5 |
+
Ensure the overall layout follows the template loosely. Make sure each recommended item has a brief but engaging introduction, highlighting why it suits the customer’s interests based on their history.
|
6 |
+
|
7 |
+
### Newsletter template
|
8 |
+
${newsletter_template}
|
9 |
+
|
10 |
+
### Customer Information
|
11 |
+
${customer_history}.
|
12 |
+
|
13 |
+
### Previous Transactions
|
14 |
+
${transactions}
|
15 |
+
|
16 |
+
### Recommended Items
|
17 |
+
${recommended_items}
|
18 |
+
|
prompt_templates/1.txt
ADDED
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
Create a personalized HTML newsletter. Start from the provided newsletter template and use the following specifications.
|
3 |
+
|
4 |
+
|
5 |
+
### Preferences
|
6 |
+
Make it engaging and informal.
|
7 |
+
Add some personal lines that create a connection with the reader.
|
8 |
+
Highlight the connection between previously bought items and recommended ones.
|
9 |
+
Don't be afraid of adding textual content!
|
10 |
+
Do not change the prices of the items.
|
11 |
+
${newsletter_settings}
|
12 |
+
|
13 |
+
|
14 |
+
### Newsletter template
|
15 |
+
Use the provided HTML structure below as the template. Integrate the provided content, following the layout as closely as possible.
|
16 |
+
${newsletter_template}
|
17 |
+
|
18 |
+
|
19 |
+
### Customer Information
|
20 |
+
${customer_info}.
|
21 |
+
|
22 |
+
|
23 |
+
### Previously Bought Items
|
24 |
+
${transactions}
|
25 |
+
|
26 |
+
|
27 |
+
### Recommended Items
|
28 |
+
List the following recommended items with descriptions, images, and links:
|
29 |
+
${recommended_items}
|
30 |
+
|
31 |
+
|
32 |
+
### Additional Features
|
33 |
+
Add a [GREETING, DISCOUNT CODE, CTA (e.g., "Shop Now" or "Learn More"), UPCOMING EVENT], if specified:
|
34 |
+
- Greeting: Use a personalized greeting, such as “Hello, [CUSTOMER NAME]!”
|
35 |
+
- Discount Code (if any): [DISCOUNT CODE or DISCOUNT DETAILS]
|
36 |
+
- Call to Action : Use a button or link with text like "[CTA TEXT]" that leads to [CTA URL]
|
37 |
+
|
38 |
+
|
prompt_templates/personalised_text_1.txt
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
You are an AI writing a personalized newsletter for a user based on their profile data. Here is the user's information:
|
2 |
+
|
3 |
+
Name: ${name}
|
4 |
+
Email: ${email}
|
5 |
+
Preferences:
|
6 |
+
- Topics of interest: ${topics}
|
7 |
+
- Products they viewed: ${products_viewed}
|
8 |
+
Interaction frequency: ${interaction_frequency}
|
9 |
+
Subscription:
|
10 |
+
- Plan: ${plan}
|
11 |
+
Location: ${location}
|
12 |
+
|
13 |
+
Write a warm, engaging, and personalized newsletter for this user. Include:
|
14 |
+
1. A personalized greeting.
|
15 |
+
2. Highlights based on their preferences.
|
16 |
+
3. Recommendations for products, articles, or upcoming events they might like.
|
17 |
+
4. A reminder of their subscription benefits.
|
18 |
+
5. A closing call-to-action encouraging further interaction.
|
19 |
+
|
20 |
+
Ensure the tone is friendly, professional, and resonates with their interests.
|
21 |
+
|
22 |
+
### Preferences
|
23 |
+
${newsletter_settings}
|
24 |
+
|
25 |
+
### Newsletter template
|
26 |
+
Use the provided HTML structure below as the template. Integrate the provided content, following the layout and formatting as closely as possible.
|
27 |
+
${newsletter_template}
|
28 |
+
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
pyyaml
|
3 |
+
openai
|
src/__pycache__/utils.cpython-311.pyc
ADDED
Binary file (7.65 kB). View file
|
|
src/__pycache__/utils.cpython-312.pyc
ADDED
Binary file (6.55 kB). View file
|
|
src/__pycache__/utils_api.cpython-311.pyc
ADDED
Binary file (7.21 kB). View file
|
|
src/__pycache__/utils_api.cpython-312.pyc
ADDED
Binary file (5.69 kB). View file
|
|
src/utils.py
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from openai import OpenAI
|
2 |
+
import os
|
3 |
+
from string import Template
|
4 |
+
import logging
|
5 |
+
|
6 |
+
|
7 |
+
class LLMHandler:
|
8 |
+
def __init__(self, api_key, model_name, default_temperature, default_max_tokens, default_system_message=None):
|
9 |
+
|
10 |
+
self.client = OpenAI(api_key=api_key)
|
11 |
+
self.model_name = model_name
|
12 |
+
self.default_temperature = default_temperature
|
13 |
+
self.default_max_tokens = default_max_tokens
|
14 |
+
self.default_system_message = default_system_message
|
15 |
+
|
16 |
+
|
17 |
+
def generate_text(self, prompt, model_name, temperature=None, max_tokens=None, system_message=None):
|
18 |
+
# prompt must be built outside
|
19 |
+
|
20 |
+
# optional parameters
|
21 |
+
model_name = model_name or self.model_name
|
22 |
+
temperature = temperature or self.default_temperature
|
23 |
+
max_tokens = max_tokens or self.default_max_tokens
|
24 |
+
system_message = system_message or self.default_system_message
|
25 |
+
|
26 |
+
# prepare messages
|
27 |
+
messages = [{"role": "user", "content": prompt}]
|
28 |
+
if system_message:
|
29 |
+
messages.insert(0, {"role": "system", "content": system_message})
|
30 |
+
|
31 |
+
# get response
|
32 |
+
logging.debug(f"Generating text with model {model_name}, temperature {temperature}, and max tokens {max_tokens}...")
|
33 |
+
logging.debug(f"Prompt: {messages}")
|
34 |
+
response = self.client.chat.completions.create(
|
35 |
+
model=model_name,
|
36 |
+
messages=messages,
|
37 |
+
temperature=temperature,
|
38 |
+
max_tokens=max_tokens
|
39 |
+
)
|
40 |
+
return response.choices[0].message.content
|
41 |
+
|
42 |
+
|
43 |
+
def format_prompt_personalsed_text(
|
44 |
+
customer_info,
|
45 |
+
recommendations,
|
46 |
+
transactions,
|
47 |
+
newsletter_settings,
|
48 |
+
newsletter_meta_info
|
49 |
+
):
|
50 |
+
|
51 |
+
# get the paths to the template files
|
52 |
+
personalised_text_prompt_template_path = newsletter_meta_info['personalised_text_prompt_template_path']
|
53 |
+
personalised_text_example_path = newsletter_meta_info['personalised_text_example_path']
|
54 |
+
|
55 |
+
|
56 |
+
# load the template from file
|
57 |
+
logging.debug(f"Loading example from {personalised_text_example_path}...")
|
58 |
+
with open(personalised_text_example_path, "r") as f:
|
59 |
+
example = Template(f.read())
|
60 |
+
|
61 |
+
# load the template from file
|
62 |
+
logging.debug(f"Loading prompt template from {personalised_text_prompt_template_path}...")
|
63 |
+
with open(personalised_text_prompt_template_path, "r") as f:
|
64 |
+
template = Template(f.read())
|
65 |
+
example = example.safe_substitute(
|
66 |
+
brand_logo=newsletter_meta_info.get("brand_logo", ""),
|
67 |
+
brand_name=newsletter_meta_info.get("brand_name", ""),
|
68 |
+
|
69 |
+
)
|
70 |
+
|
71 |
+
|
72 |
+
# Sample JSON object
|
73 |
+
user_data = {
|
74 |
+
"user": {
|
75 |
+
"name": "Sophia",
|
76 |
+
"email": "[email protected]",
|
77 |
+
"preferences": {
|
78 |
+
"favorite_styles": ["streetwear", "boho", "vintage"],
|
79 |
+
"colors": ["pastel pink", "sage green", "cream"],
|
80 |
+
"recent_buys": ["oversized hoodie", "floral maxi dress"],
|
81 |
+
"wishlist_items": ["chunky sneakers", "wide-leg jeans"],
|
82 |
+
"interests": ["seasonal trends", "sustainable fashion"]
|
83 |
+
},
|
84 |
+
"interaction_frequency": "bi-weekly",
|
85 |
+
"subscription": {
|
86 |
+
"plan": "VIP",
|
87 |
+
"signup_date": "2024-03-10"
|
88 |
+
},
|
89 |
+
"location": "New York, NY",
|
90 |
+
"age_group": "18-24"
|
91 |
+
}
|
92 |
+
}
|
93 |
+
|
94 |
+
logging.debug("Formatting the prompt...")
|
95 |
+
|
96 |
+
user = user_data["user"]
|
97 |
+
preferences = user["preferences"]
|
98 |
+
|
99 |
+
prompt = template.safe_substitute(
|
100 |
+
name=user["name"],
|
101 |
+
email=user["email"],
|
102 |
+
topics=", ".join(preferences["interests"]),
|
103 |
+
plan=user["subscription"]["plan"],
|
104 |
+
interaction_frequency=user["interaction_frequency"],
|
105 |
+
products_viewed=", ".join(preferences["wishlist_items"]),
|
106 |
+
location=user["location"],
|
107 |
+
newsletter_settings=newsletter_settings,
|
108 |
+
newsletter_template=example
|
109 |
+
)
|
110 |
+
|
111 |
+
logging.debug(f"Formatted prompt: {prompt}")
|
112 |
+
|
113 |
+
return prompt
|
114 |
+
|
115 |
+
|
116 |
+
def format_prompt(
|
117 |
+
customer_info,
|
118 |
+
recommendations,
|
119 |
+
transactions,
|
120 |
+
newsletter_settings,
|
121 |
+
newsletter_meta_info
|
122 |
+
):
|
123 |
+
|
124 |
+
# get the paths to the template files
|
125 |
+
prompt_template_path = newsletter_meta_info['prompt_template_path']
|
126 |
+
newsletter_example_path = newsletter_meta_info['newsletter_example_path']
|
127 |
+
|
128 |
+
|
129 |
+
# load the template from file
|
130 |
+
logging.debug(f"Loading example from {newsletter_example_path}...")
|
131 |
+
with open(newsletter_example_path, "r") as f:
|
132 |
+
example = Template(f.read())
|
133 |
+
example = example.safe_substitute(
|
134 |
+
brand_logo=newsletter_meta_info.get("brand_logo", ""),
|
135 |
+
brand_name=newsletter_meta_info.get("brand_name", ""),
|
136 |
+
)
|
137 |
+
|
138 |
+
# load the template from file
|
139 |
+
logging.debug(f"Loading prompt template from {prompt_template_path}...")
|
140 |
+
with open(prompt_template_path, "r") as f:
|
141 |
+
template = Template(f.read())
|
142 |
+
|
143 |
+
logging.debug("Formatting the prompt...")
|
144 |
+
prompt = template.safe_substitute(
|
145 |
+
customer_info=customer_info,
|
146 |
+
recommended_items=recommendations,
|
147 |
+
transactions=transactions,
|
148 |
+
newsletter_settings=newsletter_settings,
|
149 |
+
newsletter_template=example,
|
150 |
+
)
|
151 |
+
logging.debug(f"Formatted prompt: {prompt}")
|
152 |
+
|
153 |
+
return prompt
|
154 |
+
|
155 |
+
|
156 |
+
def load_newsletter_templates(templates_dir, num_examples=1):
|
157 |
+
|
158 |
+
# iterate over the files in the templates directory and load each template, then concatenate them into a single string
|
159 |
+
templates = []
|
160 |
+
for i, file in enumerate(os.listdir(templates_dir)):
|
161 |
+
with open(os.path.join(templates_dir, file), "r") as f:
|
162 |
+
templates.append(f.read())
|
163 |
+
if i == num_examples - 1:
|
164 |
+
break
|
165 |
+
|
166 |
+
# concatenate the templates into a single string, adding a newline and " Example numebr "
|
167 |
+
newsletter_template = "\n".join([f"Example {i+1}\n{template}\n\n\n\n" for i, template in enumerate(templates)])
|
168 |
+
|
169 |
+
return newsletter_template
|
src/utils_api.py
ADDED
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import requests
|
2 |
+
import json
|
3 |
+
import os
|
4 |
+
|
5 |
+
|
6 |
+
def make_api_request(endpoint, ids):
|
7 |
+
"""
|
8 |
+
Generic function to make API requests with error handling
|
9 |
+
|
10 |
+
:param endpoint: The specific API endpoint (e.g. 'get-images-from-id')
|
11 |
+
:param ids: List of IDs to query
|
12 |
+
:return: JSON response or None if error occurs
|
13 |
+
"""
|
14 |
+
api_key = os.environ.get("RECOMMENDER_KEY")
|
15 |
+
if not api_key:
|
16 |
+
raise ValueError("API key is not set")
|
17 |
+
headers = {
|
18 |
+
"x-api-key": api_key,
|
19 |
+
"Content-Type": "application/json"
|
20 |
+
}
|
21 |
+
|
22 |
+
payload = {"ids": ids}
|
23 |
+
|
24 |
+
try:
|
25 |
+
recommender_url = os.environ.get("RECOMMENDER_URL")
|
26 |
+
if not recommender_url:
|
27 |
+
raise ValueError("Recommender URL is not set")
|
28 |
+
response = requests.post(f"{recommender_url}/{endpoint}",
|
29 |
+
headers=headers,
|
30 |
+
data=json.dumps(payload))
|
31 |
+
|
32 |
+
response.raise_for_status() # Raise an exception for bad status codes
|
33 |
+
return response.json()
|
34 |
+
|
35 |
+
except requests.exceptions.RequestException as e:
|
36 |
+
print(f"Error occurred while calling {endpoint}: {e}")
|
37 |
+
return None
|
38 |
+
|
39 |
+
def get_images_from_id(ids):
|
40 |
+
"""Get images for specific product IDs"""
|
41 |
+
return make_api_request("get-images-from-id", ids)
|
42 |
+
|
43 |
+
|
44 |
+
def parse_article_data(article_data):
|
45 |
+
"""Parse the article data and return a dictionary"""
|
46 |
+
|
47 |
+
# get the article information
|
48 |
+
article_info = article_data['article']
|
49 |
+
aricle_id = article_info.get('article_id', 'Unknown')
|
50 |
+
article_name = article_info.get('prod_name', 'Unknown')
|
51 |
+
article_type = article_info.get('department_name', 'Unknown')
|
52 |
+
article_color = article_info.get('perceived_colour_master_name', 'Unknown')
|
53 |
+
|
54 |
+
# get the article images
|
55 |
+
article_images = get_images_from_id([aricle_id])
|
56 |
+
img_url = 'no image'
|
57 |
+
if len(article_images) > 0:
|
58 |
+
img_url = article_images[0].get('url', 'no image')
|
59 |
+
|
60 |
+
return {
|
61 |
+
"product_name": article_name,
|
62 |
+
"product_type": article_type,
|
63 |
+
"product_color": article_color,
|
64 |
+
"product_image_url": img_url
|
65 |
+
}
|
66 |
+
|
67 |
+
def parse_transaction_data(transaction_data):
|
68 |
+
"""Parse the transaction data and return a dictionary with article information."""
|
69 |
+
|
70 |
+
# Retrieve general transaction details
|
71 |
+
transaction_date = transaction_data.get('t_dat', 'Unknown')
|
72 |
+
sales_channel_id = transaction_data.get('sales_channel_id', 'Unknown')
|
73 |
+
price = transaction_data.get('price', 'Unknown')
|
74 |
+
|
75 |
+
# Get the article information
|
76 |
+
article_info = transaction_data.get('article', {})
|
77 |
+
article_id = article_info.get('article_id', 'Unknown')
|
78 |
+
article_name = article_info.get('prod_name', 'Unknown')
|
79 |
+
article_type = article_info.get('department_name', 'Unknown')
|
80 |
+
article_color = article_info.get('perceived_colour_master_name', 'Unknown')
|
81 |
+
article_detail = article_info.get('detail_desc', 'Unknown')
|
82 |
+
|
83 |
+
# Get the article images
|
84 |
+
article_images = get_images_from_id([article_id])
|
85 |
+
img_url = 'no image'
|
86 |
+
if article_images:
|
87 |
+
img_url = article_images[0].get('url', 'no image')
|
88 |
+
|
89 |
+
return {
|
90 |
+
"transaction_date": transaction_date,
|
91 |
+
"sales_channel_id": sales_channel_id,
|
92 |
+
"price": price,
|
93 |
+
"product_name": article_name,
|
94 |
+
"product_type": article_type,
|
95 |
+
"product_color": article_color,
|
96 |
+
"product_image_url": img_url,
|
97 |
+
"article_id": article_id
|
98 |
+
}
|
99 |
+
|
100 |
+
|
101 |
+
|
102 |
+
def parse_recommendations(recommendations, max_recs=4, max_transactions=2, only_with_images=True):
|
103 |
+
"""Parse the recommendations and return a list of product IDs"""
|
104 |
+
|
105 |
+
# get the client information
|
106 |
+
customer_info = recommendations[0]['customer'][0]
|
107 |
+
customer_id = customer_info.get('customer_id', 'Unknown')
|
108 |
+
customer_name = customer_info.get('name', customer_id[:8]) # TODO change to real customer name
|
109 |
+
customer_age = customer_info.get('age', 'Unknown')
|
110 |
+
|
111 |
+
customer_info = {
|
112 |
+
"customer name": customer_name,
|
113 |
+
"customer age": customer_age
|
114 |
+
}
|
115 |
+
|
116 |
+
# get the recommendations
|
117 |
+
formatted_recommendations = [parse_article_data(article) for article in recommendations[0]['prediction']]
|
118 |
+
|
119 |
+
formatted_transactions = [parse_transaction_data(transaction) for transaction in recommendations[0]['transactions']]
|
120 |
+
|
121 |
+
if only_with_images:
|
122 |
+
formatted_recommendations = [rec for rec in formatted_recommendations if rec['product_image_url'] != 'no image']
|
123 |
+
formatted_transactions = [t for t in formatted_transactions if t['product_image_url'] != 'no image']
|
124 |
+
|
125 |
+
if len(formatted_recommendations) > max_recs:
|
126 |
+
formatted_recommendations = formatted_recommendations[:max_recs]
|
127 |
+
|
128 |
+
if len(formatted_transactions) > max_transactions:
|
129 |
+
formatted_transactions = formatted_transactions[:max_transactions]
|
130 |
+
|
131 |
+
return customer_info, formatted_recommendations, formatted_transactions
|
132 |
+
|
133 |
+
def get_recommendations(customer_ids, max_recs=4, max_transactions=2):
|
134 |
+
"""Get product recommendations for specific customer IDs"""
|
135 |
+
if isinstance(customer_ids, str):
|
136 |
+
customer_ids = [customer_ids]
|
137 |
+
recommendations = make_api_request("get-recommendations", customer_ids)
|
138 |
+
if recommendations:
|
139 |
+
return parse_recommendations(recommendations, max_recs, max_transactions)
|
140 |
+
return None
|
141 |
+
|
142 |
+
|
143 |
+
if __name__ == "__main__":
|
144 |
+
# Example product IDs
|
145 |
+
product_ids = ["0110065002", "0111609001"]
|
146 |
+
|
147 |
+
# Example customer ID
|
148 |
+
customer_id = ["04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c"]
|
149 |
+
|
150 |
+
# Demonstrate different API calls
|
151 |
+
print("Images:", get_images_from_id(product_ids))
|
152 |
+
print("Recommendations:", get_recommendations(customer_id))
|
153 |
+
|
154 |
+
|