Spaces:
Sleeping
Sleeping
| 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) | |