Springs-apps/PrintProntoDemo
0
1import base642import os3import random4import re5 6import dotenv7import gradio as gr8from openai import OpenAI9 10dotenv.load_dotenv()11 12client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))13 14script_dir = os.path.dirname(os.path.abspath(__file__))15COMPANIES = ["PrintPronto", "BannerLord", "PrintMaster"]16printer_1 = """17# PrintPronto18## Product: Business cards19 20Color:21- Black (default)22- Blue23- Brown24- Orange25- Pink26 27Sizes:283.5x2: multiplier 1.0 (default)292.5x2.5: multiplier 0.95302.125x3.375: multiplier 0.931Custom size: This vendor doesn't support custom sizes32 33Material:34- Standard (default)35- Uncoated36 37Base Prices (Quantity,Price per Unit $):38100, 0.2339250, 0.240500, 0.19411000, 0.1742 43## Product: Fabric Banners44 45Sizes:462.5x4: multiplier 1.0474x4: multiplier 1.1482.5x6: multiplier 1.0 (default)492.5x8: multiplier 1.2504x6: multiplier 1.551Custom size: This vendor doesn't support custom sizes52 53Base Prices (Quantity,Price per Unit $):541, 230.00555, 210.005610, 201.505725, 195.2658 59## Product: Bumper Stickers60 61Shapes:62Rectangles: multiplier 1.063Squares: multiplier 1.064Circles: multiplier 1.065Ovals: multiplier 1.066Rounded Rectangles: multiplier 1.067Custom shape: This vendor doesn't support custom shapes68 69Sizes:702x3: multiplier 1.0712x4: multiplier 1.1723x4: multiplier 1.0 (default)733x6: multiplier 1.2742x8: multiplier 1.575Custom size: This vendor doesn't support custom sizes76 77Base Prices (Quantity,Price per Unit $):7850, 1.8079100, 1.1080250, 0.8081500, 0.44821000, 0.2483 84## Product: Paper Stickers85 86Shapes:87Rectangles: multiplier 1.0 88Squares: multiplier 1.089Circles: multiplier 1.090Ovals: multiplier 1.091Custom size: This vendor doesn't support custom shapes92 93Sizes:940.79: multiplier 1.0 (default)951: multiplier 1.1961.18: multiplier 1.0971.26: multiplier 1.2981.38: multiplier 1.599Custom size: This vendor doesn't support custom sizes100 101Material:102- Gloss Paper (default)103- Matte Paper104 105Base Prices (Quantity,Price per Unit $):106250, 0,40107500, 0.221081000, 0.121092000, 0.09110"""111 112printer_2 = """113# BannerLord114## Product: Business cards115 116Color:117- Black (default)118- Blue119 120Sizes:1213.5x2: multiplier 1.0 (default)1222.5x2.5: multiplier 0.951232.125x3.375: multiplier 0.91243x2: multiplier 1.51254x3: multiplier 1.5126Custom size: This vendor supports custom sizes with a multiplier of 1.2127 128Material:129- Standard (default)130- Uncoated131 132Base Prices (Quantity,Price per Unit $):133100, 0.25134250, 0.22135500, 0.21361000, 0.19137 138## Product: Fabric Banners139 140Sizes:1412.5x4: multiplier 1.01424x4: multiplier 1.11432.5x6: multiplier 1.0 (default)1442.5x8: multiplier 1.21454x6: multiplier 1.5146Custom size: This vendor supports custom sizes with a multiplier of 1.2147 148Base Prices (Quantity,Price per Unit $):1491, 228.001505, 205.0015110, 204.9015225, 193.56153 154## Product: Bumper Stickers155 156Shapes:157Rectangles: multiplier 1.0 158Squares: multiplier 1.0159Circles: multiplier 1.0160Ovals: multiplier 1.0161Rounded Rectangles: multiplier 1.0162Custom shape: This vendor supports custom shapes with a multiplier of 1.2163 164Sizes:1652x3: multiplier 1.01662x4: multiplier 1.11673x4: multiplier 1.0 (default)1683x6: multiplier 1.21692x8: multiplier 1.5170Custom size: This vendor supports custom sizes with a multiplier of 1.2171 172Base Prices (Quantity,Price per Unit $):17350, 1.90174100, 1.15175250, 0.85176500, 0.451771000, 0.25178 179## Product: Paper Stickers180 181Shapes:182Rectangles: multiplier 1.0183Squares: multiplier 1.0184Circles: multiplier 1.0185Ovals: multiplier 1.0186Custom size: This vendor supports custom sizes with a multiplier of 1.2187 188Sizes:1890.79: multiplier 1.0 (default)1901: multiplier 1.11911.18: multiplier 1.01921.26: multiplier 1.21931.38: multiplier 1.5194Custom size: This vendor supports custom sizes with a multiplier of 1.2195 196Material:197- Gloss Paper (default)198- Matte Paper199 200Base Prices (Quantity,Price per Unit $):201250, 0.42202500, 0.232031000, 0.132042000, 0.10205"""206 207printer_3 = """208# PrintMaster209## Product: Business cards210 211Color:212- Black (default)213- Blue214- Brown215 216Sizes:2173.5x2: multiplier 1.0 (default)2182.5x2.5: multiplier 0.952192.125x3.375: multiplier 0.92203x2: multiplier 1.52214x3: multiplier 1.5222Custom size: This vendor supports custom sizes with a multiplier of 1.2223 224Material:225- Standard (default)226- Uncoated227 228Base Prices (Quantity,Price per Unit $):229100, 0.21230250, 0.2231500, 0.192321000, 0.18233 234## Product: Fabric Banners235 236Sizes:2372.5x4: multiplier 1.0 (default)2384x4: multiplier 1.12392.5x6: multiplier 1.02402.5x8: multiplier 1.22414x6: multiplier 1.5242Custom size: This vendor supports custom sizes with a multiplier of 1.2243 244Base Prices (Quantity,Price per Unit $):2451, 231.002465, 209.5024710, 203.4524825, 194.00249 250## Product: Bumper Stickers251 252Shapes:253Rectangles: multiplier 1.0254Squares: multiplier 1.0255Circles: multiplier 1.0256Ovals: multiplier 1.0257Rounded Rectangles: multiplier 1.0258Custom shape: This vendor supports custom shapes with a multiplier of 1.2259 260Sizes:2612x3: multiplier 1.0 (default)2622x4: multiplier 1.12633x4: multiplier 1.02643x6: multiplier 1.22652x8: multiplier 1.5266Custom size: This vendor supports custom sizes with a multiplier of 1.2267 268Base Prices (Quantity,Price per Unit $):26950, 1.85270100, 1.10271250, 0.82272500, 0.432731000, 0.23274 275## Product: Paper Stickers276 277Shapes:278Rectangles: multiplier 1.0279Squares: multiplier 1.0280Circles: multiplier 1.0281Ovals: multiplier 1.0282Custom size: This vendor supports custom sizes with a multiplier of 1.2283 284Sizes:2850.79: multiplier 1.0 (default)2861: multiplier 1.12871.18: multiplier 1.02881.26: multiplier 1.22891.38: multiplier 1.5290Custom size: This vendor supports custom sizes with a multiplier of 1.2291 292Material:293- Gloss Paper (default)294- Matte Paper295 296Base Prices (Quantity,Price per Unit $):297250, 0.40298500, 0.212991000, 0.113002000, 0.08301"""302 303IMAGES = {304 "Business Cards": {305 "images": [306 os.path.join(script_dir, "public/src/painted-edge-business-card-05.png"),307 os.path.join(308 script_dir,309 "public/src/painted-edge-cards---w-brown-box-_4-4-color_.png",310 ),311 os.path.join(script_dir, "public/src/silk-business-card-03_1.png"),312 ],313 "links": [314 "https://www.example.com/product_image.jpg",315 "https://www.example.com/product_image.jpg",316 "https://www.example.com/product_image.jpg",317 ],318 },319 "Fabric Banners": {320 "images": [321 os.path.join(script_dir, "public/src/03.jpg"),322 os.path.join(323 script_dir,324 "public/src/fabric-banner-01.webp",325 ),326 os.path.join(script_dir, "public/src/fabric-banner-02.webp"),327 ],328 "links": [329 "https://www.example.com/product_image.jpg",330 "https://www.example.com/product_image.jpg",331 "https://www.example.com/product_image.jpg",332 ],333 },334 "Bumper Stickers": {335 "images": [336 os.path.join(script_dir, "public/src/clear-bumper-sticker.jpg"),337 os.path.join(338 script_dir,339 "public/src/copy_of_untitled_design_9_.webp",340 ),341 os.path.join(script_dir, "public/src/removable-bumper-stickers.jpg"),342 ],343 "links": [344 "https://www.example.com/product_image.jpg",345 "https://www.example.com/product_image.jpg",346 "https://www.example.com/product_image.jpg",347 ],348 },349 "Paper Stickers": {350 "images": [351 os.path.join(script_dir, "public/src/us_images_1_.webp"),352 os.path.join(353 script_dir,354 "public/src/copy_of_us_images_1__1.webp",355 ),356 os.path.join(script_dir, "public/src/copy_of_us_images_1__1.webp"),357 ],358 "links": [359 "https://www.example.com/product_image.jpg",360 "https://www.example.com/product_image.jpg",361 "https://www.example.com/product_image.jpg",362 ],363 },364}365 366 367def find_best_price(request):368 chat_prompt = (369 "You are a customer assistant tasked with finding the best price for multiple items from different printers.\n"370 "Here are the prices and options from the printers:\n"371 f"Printer 1: {printer_1}\n"372 f"Printer 2: {printer_2}\n"373 f"Printer 3: {printer_3}\n"374 "For each product in the request, follow these steps:\n"375 "1. Verify if the requested size is available. If the requested size is unavailable, check if the vendor supports custom sizes.\n"376 "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"377 "3. Apply the custom size multiplier only under the following conditions:\n"378 "- The user explicitly requests a specific size.\n"379 "- The requested size is not standard (i.e., it is custom).\n"380 "- The vendor supports custom sizes.\n"381 "4. Calculate the final price by:\n"382 " - Finding the nearest lower quantity in the base price list.\n"383 " - Multiplying by the size multiplier\n"384 " - Multiplying by the user requested amount. IMPORTANT. Do not multiply by nearest lower quantity!\n"385 "5. Do not use linear interpolation for quantities - use the nearest lower quantity pricing.\n\n"386 "Example:\n"387 "User: I want 25 banners in size 6x6, 2000 business cards and 5000 circle stickers in size 6x6 on matte paper\n"388 "Answer:\n"389 "Banners:\n"390 "- PrintPronto: Doesn't support custom size\n"391 "- BannerLord: amount (25) * multiplier for custom size (1.2) * price per unit for nearest pack size ($193.56). Total for 25: $5806.80\n"392 "- 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"393 "Business cards:\n"394 "- PrintPronto: amount (2000) * multiplier for default size (1.0) * price per unit for nearest pack size ($0.17). Total for 2000: $340.00\n"395 "- BannerLord: amount (2000) * multiplier for default size (1.0) * price per unit for nearest pack size ($0.19). Total for 2000: $380.00\n"396 "- 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"397 " Paper stickers:\n"398 "- PrintPronto: Doesn't support custom size\n"399 "- 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"400 "- 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"401 "Your response should display ONLY final pricing for each available option, nothing more.\n"402 "At the end of your response, write summary in the following format:\n"403 "Full quote (all products): *all items, that user's requested*\n"404 "- Printer1: $price of full request\n"405 "- Printer2: $price of full request\n"406 "- Printer3: $price of full request\n"407 )408 409 chat_response = client.chat.completions.create(410 model="gpt-4o-mini",411 messages=[412 {"role": "system", "content": chat_prompt},413 {"role": "user", "content": request},414 ],415 temperature=0.5,416 )417 418 chat_text = chat_response.choices[0].message.content419 chat_text = chat_text.replace("###", "")420 421 images_to_use = extract_requested_products(422 request=request, product_names=list(IMAGES.keys())423 )424 if images_to_use is None:425 html_page = (426 "<html><body><h3>Requested product is not available.</h3></body></html>"427 )428 chat_text = "Requested product is not available."429 return html_page, chat_text430 431 html_page = render_html_page(chat_text, images_to_use)432 433 return html_page, chat_text434 435 436def extract_total_prices(text):437 """438 Extracts total prices and availability information from a text block,439 preserving the order of appearance.440 441 Args:442 text: The input text.443 444 Returns:445 A list of strings containing prices and availability messages in order.446 """447 results = []448 # Combine patterns using the OR operator | and capture groups449 pattern = (450 r"(Total for \d+: (\$\d+(?:,\d{3})*(?:\.\d{2})?))|(Doesn't support custom size)"451 )452 453 matches = re.findall(pattern, text)454 455 for match in matches:456 # Check which group matched457 if match[0]: # Price match458 results.append(match[1])459 elif match[2]: # Availability match460 results.append(match[2])461 return results462 463 464def extract_requested_products(request, product_names):465 product_list = "\n".join(f"- {product}" for product in product_names)466 prompt = (467 "Here are the available product names:\n"468 f"{product_list}\n"469 "Identify any products explicitly or implicitly mentioned in the user's request. "470 "If a product is mentioned in different sizes, consider each size as a separate product and include them in the list. "471 "However, do not include duplicate entries of the same product unless explicitly mentioned multiple times in the request. "472 "If no products match, respond with 'None'. Output the result as a Python list. "473 "Your response should contain only the list or 'None'."474 )475 476 response = client.chat.completions.create(477 model="gpt-4o-mini",478 messages=[479 {"role": "system", "content": prompt},480 {"role": "user", "content": request},481 ],482 temperature=0.1,483 )484 response_text = response.choices[0].message.content485 return eval(response_text)486 487 488def render_html_page(chat_text, images_to_use):489 if images_to_use is None:490 return "<html><body><h3>Requested product is not available.</h3></body></html>"491 492 html_content = """493 <html>494 <head>495 <title>Product Cards</title>496 <style>497 .product-card {498 border: 1px solid #ccc;499 border-radius: 5px;500 padding: 16px;501 margin: 16px;502 text-align: center;503 width: 200px;504 display: inline-block;505 }506 .product-card img {507 max-width: 100%;508 height: auto;509 }510 .product-card button {511 background-color: #4CAF50;512 color: white;513 padding: 10px 20px;514 border: none;515 border-radius: 5px;516 cursor: pointer;517 }518 .product-card button:hover {519 background-color: #45a049;520 }521 .summary {522 border-top: 2px solid #ccc;523 padding-top: 16px;524 margin-top: 20px;525 font-family: Arial, sans-serif;526 }527 </style>528 </head>529 <body>530 """531 532 # Assuming processed_prices is a flat list533 processed_prices = extract_total_prices(chat_text)534 535 # Process images and links536 processed_images = [537 image for img in images_to_use for image in IMAGES[img]["images"]538 ]539 processed_links = [link for img in images_to_use for link in IMAGES[img]["links"]]540 541 # Initialize labels for all products542 labels = [""] * len(processed_prices)543 544 # Group prices in subsets of 3 and assign labels545 for group_start in range(0, len(processed_prices), 3):546 subset_indices = list(547 range(group_start, min(group_start + 3, len(processed_prices)))548 )549 subset_prices = [processed_prices[i] for i in subset_indices]550 551 # Find the index of the lowest price in this subset552 min_price_index = subset_indices[subset_prices.index(min(subset_prices))]553 labels[min_price_index] = "<b>BEST VALUE</b>"554 555 # Assign remaining labels randomly556 remaining_indices = [i for i in subset_indices if i != min_price_index]557 random.shuffle(remaining_indices)558 labels[remaining_indices[0]] = "<b>BEST QUALITY</b>"559 labels[remaining_indices[1]] = (560 "<b>FAST PRODUCTION</b>" if len(remaining_indices) > 1 else ""561 )562 563 # Generate HTML content564 for i in range(len(processed_images)):565 image = processed_images[i]566 with open(image, "rb") as img_file:567 base64_image = base64.b64encode(img_file.read()).decode("utf-8")568 569 # Add price with label570 price_with_label = (571 f"{labels[i]}<br>{COMPANIES[i % 3]} "572 f"{images_to_use[i // 3].lower()}: {processed_prices[i]}"573 )574 575 html_content += f"""576 <div class="product-card">577 <img src="data:image/jpeg;base64,{base64_image}" alt="Product Image">578 <p>{price_with_label}</p>579 <a href="{processed_links[i]}" target="_blank"><button>Buy Now</button></a>580 </div>581 """582 583 # Extract the summary from chat_text584 # print(chat_text)585 summary_start = chat_text.find("Full quote (all products):")586 # print(summary_start)587 if summary_start != -1:588 summary_text = chat_text[summary_start:].strip()589 html_content += f"""590 <div class="summary">591 <h3>Quote Summary</h3>592 <pre>{summary_text}</pre>593 </div>594 """595 596 html_content += "</body></html>"597 return html_content598 599 600logo = os.path.join(script_dir, "public/src/logo.svg")601with open(logo, "rb") as logo_file:602 base64_logo = base64.b64encode(logo_file.read()).decode("utf-8")603 604 605iface = gr.Interface(606 fn=find_best_price,607 inputs=gr.Textbox(lines=3, placeholder="Enter what are you looking for"),608 outputs=[609 gr.HTML(label="Product Image"),610 gr.Textbox(label="AI response: text mode"),611 ],612 title="Get Instant Quote",613 description=f"""614 <div style="display: flex; justify-content: center; align-items: center; text-align: center; margin-bottom: 20px;">615 <img src="data:image/svg+xml;base64,{base64_logo}" alt="Logo" style="width: 150px; height: auto;">616 </div>617 """,618 submit_btn="Get quote",619)620 621 622def auth_function(username, password):623 # valid_users = {"admin": "demo4anthony1", "123": "123"}624 valid_users = {"admin": "demo4anthony1"}625 return username in valid_users and valid_users[username] == password626 627 628iface.launch(auth=auth_function, share=True, ssr_mode=False)629 