Spaces:
Running
Running
import base64 | |
import os | |
import random | |
import re | |
import dotenv | |
import gradio as gr | |
from openai import OpenAI | |
dotenv.load_dotenv() | |
client = OpenAI(api_key=os.getenv("OPENAI_API_KEY")) | |
script_dir = os.path.dirname(os.path.abspath(__file__)) | |
COMPANIES = ["PrintPronto", "BannerLord", "PrintMaster"] | |
printer_1 = """ | |
# PrintPronto | |
## Product: Business cards | |
Color: | |
- Black (default) | |
- Blue | |
- Brown | |
- Orange | |
- Pink | |
Sizes: | |
3.5x2: multiplier 1.0 (default) | |
2.5x2.5: multiplier 0.95 | |
2.125x3.375: multiplier 0.9 | |
Custom size: This vendor doesn't support custom sizes | |
Material: | |
- Standard (default) | |
- Uncoated | |
Base Prices (Quantity,Price per Unit $): | |
100, 0.23 | |
250, 0.2 | |
500, 0.19 | |
1000, 0.17 | |
## Product: Fabric Banners | |
Sizes: | |
2.5x4: multiplier 1.0 | |
4x4: multiplier 1.1 | |
2.5x6: multiplier 1.0 (default) | |
2.5x8: multiplier 1.2 | |
4x6: multiplier 1.5 | |
Custom size: This vendor doesn't support custom sizes | |
Base Prices (Quantity,Price per Unit $): | |
1, 230.00 | |
5, 210.00 | |
10, 201.50 | |
25, 195.26 | |
## Product: Bumper Stickers | |
Shapes: | |
Rectangles: multiplier 1.0 | |
Squares: multiplier 1.0 | |
Circles: multiplier 1.0 | |
Ovals: multiplier 1.0 | |
Rounded Rectangles: multiplier 1.0 | |
Custom shape: This vendor doesn't support custom shapes | |
Sizes: | |
2x3: multiplier 1.0 | |
2x4: multiplier 1.1 | |
3x4: multiplier 1.0 (default) | |
3x6: multiplier 1.2 | |
2x8: multiplier 1.5 | |
Custom size: This vendor doesn't support custom sizes | |
Base Prices (Quantity,Price per Unit $): | |
50, 1.80 | |
100, 1.10 | |
250, 0.80 | |
500, 0.44 | |
1000, 0.24 | |
## Product: Paper Stickers | |
Shapes: | |
Rectangles: multiplier 1.0 | |
Squares: multiplier 1.0 | |
Circles: multiplier 1.0 | |
Ovals: multiplier 1.0 | |
Custom size: This vendor doesn't support custom shapes | |
Sizes: | |
0.79: multiplier 1.0 (default) | |
1: multiplier 1.1 | |
1.18: multiplier 1.0 | |
1.26: multiplier 1.2 | |
1.38: multiplier 1.5 | |
Custom size: This vendor doesn't support custom sizes | |
Material: | |
- Gloss Paper (default) | |
- Matte Paper | |
Base Prices (Quantity,Price per Unit $): | |
250, 0,40 | |
500, 0.22 | |
1000, 0.12 | |
2000, 0.09 | |
""" | |
printer_2 = """ | |
# BannerLord | |
## Product: Business cards | |
Color: | |
- Black (default) | |
- Blue | |
Sizes: | |
3.5x2: multiplier 1.0 (default) | |
2.5x2.5: multiplier 0.95 | |
2.125x3.375: multiplier 0.9 | |
3x2: multiplier 1.5 | |
4x3: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Material: | |
- Standard (default) | |
- Uncoated | |
Base Prices (Quantity,Price per Unit $): | |
100, 0.25 | |
250, 0.22 | |
500, 0.2 | |
1000, 0.19 | |
## Product: Fabric Banners | |
Sizes: | |
2.5x4: multiplier 1.0 | |
4x4: multiplier 1.1 | |
2.5x6: multiplier 1.0 (default) | |
2.5x8: multiplier 1.2 | |
4x6: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Base Prices (Quantity,Price per Unit $): | |
1, 228.00 | |
5, 205.00 | |
10, 204.90 | |
25, 193.56 | |
## Product: Bumper Stickers | |
Shapes: | |
Rectangles: multiplier 1.0 | |
Squares: multiplier 1.0 | |
Circles: multiplier 1.0 | |
Ovals: multiplier 1.0 | |
Rounded Rectangles: multiplier 1.0 | |
Custom shape: This vendor supports custom shapes with a multiplier of 1.2 | |
Sizes: | |
2x3: multiplier 1.0 | |
2x4: multiplier 1.1 | |
3x4: multiplier 1.0 (default) | |
3x6: multiplier 1.2 | |
2x8: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Base Prices (Quantity,Price per Unit $): | |
50, 1.90 | |
100, 1.15 | |
250, 0.85 | |
500, 0.45 | |
1000, 0.25 | |
## Product: Paper Stickers | |
Shapes: | |
Rectangles: multiplier 1.0 | |
Squares: multiplier 1.0 | |
Circles: multiplier 1.0 | |
Ovals: multiplier 1.0 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Sizes: | |
0.79: multiplier 1.0 (default) | |
1: multiplier 1.1 | |
1.18: multiplier 1.0 | |
1.26: multiplier 1.2 | |
1.38: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Material: | |
- Gloss Paper (default) | |
- Matte Paper | |
Base Prices (Quantity,Price per Unit $): | |
250, 0.42 | |
500, 0.23 | |
1000, 0.13 | |
2000, 0.10 | |
""" | |
printer_3 = """ | |
# PrintMaster | |
## Product: Business cards | |
Color: | |
- Black (default) | |
- Blue | |
- Brown | |
Sizes: | |
3.5x2: multiplier 1.0 (default) | |
2.5x2.5: multiplier 0.95 | |
2.125x3.375: multiplier 0.9 | |
3x2: multiplier 1.5 | |
4x3: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Material: | |
- Standard (default) | |
- Uncoated | |
Base Prices (Quantity,Price per Unit $): | |
100, 0.21 | |
250, 0.2 | |
500, 0.19 | |
1000, 0.18 | |
## Product: Fabric Banners | |
Sizes: | |
2.5x4: multiplier 1.0 (default) | |
4x4: multiplier 1.1 | |
2.5x6: multiplier 1.0 | |
2.5x8: multiplier 1.2 | |
4x6: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Base Prices (Quantity,Price per Unit $): | |
1, 231.00 | |
5, 209.50 | |
10, 203.45 | |
25, 194.00 | |
## Product: Bumper Stickers | |
Shapes: | |
Rectangles: multiplier 1.0 | |
Squares: multiplier 1.0 | |
Circles: multiplier 1.0 | |
Ovals: multiplier 1.0 | |
Rounded Rectangles: multiplier 1.0 | |
Custom shape: This vendor supports custom shapes with a multiplier of 1.2 | |
Sizes: | |
2x3: multiplier 1.0 (default) | |
2x4: multiplier 1.1 | |
3x4: multiplier 1.0 | |
3x6: multiplier 1.2 | |
2x8: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Base Prices (Quantity,Price per Unit $): | |
50, 1.85 | |
100, 1.10 | |
250, 0.82 | |
500, 0.43 | |
1000, 0.23 | |
## Product: Paper Stickers | |
Shapes: | |
Rectangles: multiplier 1.0 | |
Squares: multiplier 1.0 | |
Circles: multiplier 1.0 | |
Ovals: multiplier 1.0 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Sizes: | |
0.79: multiplier 1.0 (default) | |
1: multiplier 1.1 | |
1.18: multiplier 1.0 | |
1.26: multiplier 1.2 | |
1.38: multiplier 1.5 | |
Custom size: This vendor supports custom sizes with a multiplier of 1.2 | |
Material: | |
- Gloss Paper (default) | |
- Matte Paper | |
Base Prices (Quantity,Price per Unit $): | |
250, 0.40 | |
500, 0.21 | |
1000, 0.11 | |
2000, 0.08 | |
""" | |
IMAGES = { | |
"Business Cards": { | |
"images": [ | |
os.path.join(script_dir, "public/src/painted-edge-business-card-05.png"), | |
os.path.join( | |
script_dir, | |
"public/src/painted-edge-cards---w-brown-box-_4-4-color_.png", | |
), | |
os.path.join(script_dir, "public/src/silk-business-card-03_1.png"), | |
], | |
"links": [ | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
], | |
}, | |
"Fabric Banners": { | |
"images": [ | |
os.path.join(script_dir, "public/src/03.jpg"), | |
os.path.join( | |
script_dir, | |
"public/src/fabric-banner-01.webp", | |
), | |
os.path.join(script_dir, "public/src/fabric-banner-02.webp"), | |
], | |
"links": [ | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
], | |
}, | |
"Bumper Stickers": { | |
"images": [ | |
os.path.join(script_dir, "public/src/clear-bumper-sticker.jpg"), | |
os.path.join( | |
script_dir, | |
"public/src/copy_of_untitled_design_9_.webp", | |
), | |
os.path.join(script_dir, "public/src/removable-bumper-stickers.jpg"), | |
], | |
"links": [ | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
], | |
}, | |
"Paper Stickers": { | |
"images": [ | |
os.path.join(script_dir, "public/src/us_images_1_.webp"), | |
os.path.join( | |
script_dir, | |
"public/src/copy_of_us_images_1__1.webp", | |
), | |
os.path.join(script_dir, "public/src/copy_of_us_images_1__1.webp"), | |
], | |
"links": [ | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
"https://www.example.com/product_image.jpg", | |
], | |
}, | |
} | |
def find_best_price(request): | |
chat_prompt = ( | |
"You are a customer assistant tasked with finding the best price for multiple items from different printers.\n" | |
"Here are the prices and options from the printers:\n" | |
f"Printer 1: {printer_1}\n" | |
f"Printer 2: {printer_2}\n" | |
f"Printer 3: {printer_3}\n" | |
"For each product in the request, follow these steps:\n" | |
"1. Verify if the requested size is available. If the requested size is unavailable, check if the vendor supports custom sizes.\n" | |
"2. If the user has not specified one or more parameters (e.g., size, shape), use the vendor's default values for those parameters.\n" | |
"3. Apply the custom size multiplier only under the following conditions:\n" | |
"- The user explicitly requests a specific size.\n" | |
"- The requested size is not standard (i.e., it is custom).\n" | |
"- The vendor supports custom sizes.\n" | |
"4. Calculate the final price by:\n" | |
" - Finding the nearest lower quantity in the base price list.\n" | |
" - Multiplying by the size multiplier\n" | |
" - Multiplying by the user requested amount. IMPORTANT. Do not multiply by nearest lower quantity!\n" | |
"5. Do not use linear interpolation for quantities - use the nearest lower quantity pricing.\n\n" | |
"Example:\n" | |
"User: I want 25 banners in size 6x6, 2000 business cards and 5000 circle stickers in size 6x6 on matte paper\n" | |
"Answer:\n" | |
"Banners:\n" | |
"- PrintPronto: Doesn't support custom size\n" | |
"- BannerLord: amount (25) * multiplier for custom size (1.2) * price per unit for nearest pack size ($193.56). Total for 25: $5806.80\n" | |
"- PrintMaster: amount (25) * multiplier for custom size (1.2) * price per unit for nearest pack size ($194.00). Total for 25: $5820.00\n\n" | |
"Business cards:\n" | |
"- PrintPronto: amount (2000) * multiplier for default size (1.0) * price per unit for nearest pack size ($0.17). Total for 2000: $340.00\n" | |
"- BannerLord: amount (2000) * multiplier for default size (1.0) * price per unit for nearest pack size ($0.19). Total for 2000: $380.00\n" | |
"- PrintMaster: amount (2000) * multiplier for default size (1.0) * price per unit for nearest pack size ($0.18). Total for 2000: $360.00\n\n" | |
" Paper stickers:\n" | |
"- PrintPronto: Doesn't support custom size\n" | |
"- BannerLord: amount (5000) * multiplier for circle shape (1.0) * multiplier for custom size (1.2) * price per unit for nearest pack size ($0.13). Total for 5000: $780.00\n" | |
"- PrintMaster: amount (5000) * multiplier for circle shape (1.0) * multiplier for custom size (1.2) * price per unit for nearest pack size ($0.11). Total for 5000: $660.00\n\n" | |
"Your response should display ONLY final pricing for each available option, nothing more.\n" | |
"At the end of your response, write summary in the following format:\n" | |
"Full quote (all products): *all items, that user's requested*\n" | |
"- Printer1: $price of full request\n" | |
"- Printer2: $price of full request\n" | |
"- Printer3: $price of full request\n" | |
) | |
chat_response = client.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=[ | |
{"role": "system", "content": chat_prompt}, | |
{"role": "user", "content": request}, | |
], | |
temperature=0.5, | |
) | |
chat_text = chat_response.choices[0].message.content | |
chat_text = chat_text.replace("###", "") | |
images_to_use = extract_requested_products( | |
request=request, product_names=list(IMAGES.keys()) | |
) | |
if images_to_use is None: | |
html_page = ( | |
"<html><body><h3>Requested product is not available.</h3></body></html>" | |
) | |
chat_text = "Requested product is not available." | |
return html_page, chat_text | |
html_page = render_html_page(chat_text, images_to_use) | |
return html_page, chat_text | |
def extract_total_prices(text): | |
""" | |
Extracts total prices and availability information from a text block, | |
preserving the order of appearance. | |
Args: | |
text: The input text. | |
Returns: | |
A list of strings containing prices and availability messages in order. | |
""" | |
results = [] | |
# Combine patterns using the OR operator | and capture groups | |
pattern = ( | |
r"(Total for \d+: (\$\d+(?:,\d{3})*(?:\.\d{2})?))|(Doesn't support custom size)" | |
) | |
matches = re.findall(pattern, text) | |
for match in matches: | |
# Check which group matched | |
if match[0]: # Price match | |
results.append(match[1]) | |
elif match[2]: # Availability match | |
results.append(match[2]) | |
return results | |
def extract_requested_products(request, product_names): | |
product_list = "\n".join(f"- {product}" for product in product_names) | |
prompt = ( | |
"Here are the available product names:\n" | |
f"{product_list}\n" | |
"Identify any products explicitly or implicitly mentioned in the user's request. " | |
"If a product is mentioned in different sizes, consider each size as a separate product and include them in the list. " | |
"However, do not include duplicate entries of the same product unless explicitly mentioned multiple times in the request. " | |
"If no products match, respond with 'None'. Output the result as a Python list. " | |
"Your response should contain only the list or 'None'." | |
) | |
response = client.chat.completions.create( | |
model="gpt-4o-mini", | |
messages=[ | |
{"role": "system", "content": prompt}, | |
{"role": "user", "content": request}, | |
], | |
temperature=0.1, | |
) | |
response_text = response.choices[0].message.content | |
return eval(response_text) | |
def render_html_page(chat_text, images_to_use): | |
if images_to_use is None: | |
return "<html><body><h3>Requested product is not available.</h3></body></html>" | |
html_content = """ | |
<html> | |
<head> | |
<title>Product Cards</title> | |
<style> | |
.product-card { | |
border: 1px solid #ccc; | |
border-radius: 5px; | |
padding: 16px; | |
margin: 16px; | |
text-align: center; | |
width: 200px; | |
display: inline-block; | |
} | |
.product-card img { | |
max-width: 100%; | |
height: auto; | |
} | |
.product-card button { | |
background-color: #4CAF50; | |
color: white; | |
padding: 10px 20px; | |
border: none; | |
border-radius: 5px; | |
cursor: pointer; | |
} | |
.product-card button:hover { | |
background-color: #45a049; | |
} | |
.summary { | |
border-top: 2px solid #ccc; | |
padding-top: 16px; | |
margin-top: 20px; | |
font-family: Arial, sans-serif; | |
} | |
</style> | |
</head> | |
<body> | |
""" | |
# Assuming processed_prices is a flat list | |
processed_prices = extract_total_prices(chat_text) | |
# Process images and links | |
processed_images = [ | |
image for img in images_to_use for image in IMAGES[img]["images"] | |
] | |
processed_links = [link for img in images_to_use for link in IMAGES[img]["links"]] | |
# Initialize labels for all products | |
labels = [""] * len(processed_prices) | |
# Group prices in subsets of 3 and assign labels | |
for group_start in range(0, len(processed_prices), 3): | |
subset_indices = list( | |
range(group_start, min(group_start + 3, len(processed_prices))) | |
) | |
subset_prices = [processed_prices[i] for i in subset_indices] | |
# Find the index of the lowest price in this subset | |
min_price_index = subset_indices[subset_prices.index(min(subset_prices))] | |
labels[min_price_index] = "<b>BEST VALUE</b>" | |
# Assign remaining labels randomly | |
remaining_indices = [i for i in subset_indices if i != min_price_index] | |
random.shuffle(remaining_indices) | |
labels[remaining_indices[0]] = "<b>BEST QUALITY</b>" | |
labels[remaining_indices[1]] = ( | |
"<b>FAST PRODUCTION</b>" if len(remaining_indices) > 1 else "" | |
) | |
# Generate HTML content | |
for i in range(len(processed_images)): | |
image = processed_images[i] | |
with open(image, "rb") as img_file: | |
base64_image = base64.b64encode(img_file.read()).decode("utf-8") | |
# Add price with label | |
price_with_label = ( | |
f"{labels[i]}<br>{COMPANIES[i % 3]} " | |
f"{images_to_use[i // 3].lower()}: {processed_prices[i]}" | |
) | |
html_content += f""" | |
<div class="product-card"> | |
<img src="data:image/jpeg;base64,{base64_image}" alt="Product Image"> | |
<p>{price_with_label}</p> | |
<a href="{processed_links[i]}" target="_blank"><button>Buy Now</button></a> | |
</div> | |
""" | |
# Extract the summary from chat_text | |
# print(chat_text) | |
summary_start = chat_text.find("Full quote (all products):") | |
# print(summary_start) | |
if summary_start != -1: | |
summary_text = chat_text[summary_start:].strip() | |
html_content += f""" | |
<div class="summary"> | |
<h3>Quote Summary</h3> | |
<pre>{summary_text}</pre> | |
</div> | |
""" | |
html_content += "</body></html>" | |
return html_content | |
logo = os.path.join(script_dir, "public/src/logo.svg") | |
with open(logo, "rb") as logo_file: | |
base64_logo = base64.b64encode(logo_file.read()).decode("utf-8") | |
iface = gr.Interface( | |
fn=find_best_price, | |
inputs=gr.Textbox(lines=3, placeholder="Enter what are you looking for"), | |
outputs=[ | |
gr.HTML(label="Product Image"), | |
gr.Textbox(label="AI response: text mode"), | |
], | |
title="Get Instant Quote", | |
description=f""" | |
<div style="display: flex; justify-content: center; align-items: center; text-align: center; margin-bottom: 20px;"> | |
<img src="data:image/svg+xml;base64,{base64_logo}" alt="Logo" style="width: 150px; height: auto;"> | |
</div> | |
""", | |
submit_btn="Get quote", | |
) | |
def auth_function(username, password): | |
# valid_users = {"admin": "demo4anthony1", "123": "123"} | |
valid_users = {"admin": "demo4anthony1"} | |
return username in valid_users and valid_users[username] == password | |
iface.launch(auth=auth_function, share=True, ssr_mode=False) | |