File size: 10,650 Bytes
44ee360
 
19eff26
81121a5
23a6ee2
233ed00
c1f44f1
 
44ee360
 
 
eca2a9a
 
f756e36
81121a5
44ee360
 
 
23a6ee2
 
 
 
 
 
 
81121a5
44ee360
fc418b7
81121a5
19eff26
 
 
 
 
 
 
 
 
 
233ed00
19eff26
 
 
 
233ed00
19eff26
 
 
 
 
 
 
 
23a6ee2
 
233ed00
19eff26
23a6ee2
 
81121a5
23a6ee2
fc418b7
44ee360
06154c7
81121a5
eca2a9a
 
81121a5
eca2a9a
81121a5
eca2a9a
1187a57
eca2a9a
 
 
 
 
 
 
 
 
 
f756e36
81121a5
 
 
eca2a9a
 
81121a5
 
 
 
 
eca2a9a
81121a5
 
 
 
 
 
2713cdf
 
 
 
 
 
 
 
81121a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eca2a9a
81121a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eca2a9a
81121a5
 
 
 
 
 
eca2a9a
44ee360
81121a5
233ed00
23a6ee2
 
 
eca2a9a
81121a5
eca2a9a
81121a5
eca2a9a
460d20c
81121a5
460d20c
81121a5
 
 
 
 
f756e36
81121a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f756e36
81121a5
 
f756e36
 
81121a5
 
f756e36
 
81121a5
eca2a9a
81121a5
 
 
 
ae96e41
81121a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
22de171
81121a5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
eca2a9a
81121a5
44ee360
23a6ee2
81121a5
 
233ed00
 
81121a5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import os
import logging
import socket
import io
import requests
import dns.resolver
import pandas as pd
import google.generativeai as genai
from heyoo import WhatsApp
from dotenv import load_dotenv
from flask import Flask, request, make_response
from typing import Dict, List, Union
import json
from PIL import Image

# Initialize Flask App
app = Flask(__name__)

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
logger = logging.getLogger(__name__)

# Load environment variables
load_dotenv()

# DNS Configuration
nameserver1 = os.getenv('nameserver1', '8.8.8.8')
nameserver2 = os.getenv('nameserver2', '8.8.4.4')

def setup_dns():
    """Configure DNS resolution globally"""
    resolver = dns.resolver.Resolver()
    resolver.nameservers = [nameserver1, nameserver2]
    
    _orig_getaddrinfo = socket.getaddrinfo
    def new_getaddrinfo(*args, **kwargs):
        try:
            if args and args[0] == 'graph.facebook.com':
                answers = resolver.resolve('graph.facebook.com', 'A')
                ip = str(answers[0])
                return _orig_getaddrinfo(ip, *args[1:], **kwargs)
        except Exception as e:
            logger.error(f"DNS resolution failed: {e}")
        return _orig_getaddrinfo(*args, **kwargs)
    
    socket.getaddrinfo = new_getaddrinfo

setup_dns()

# Initialize WhatsApp
try:
    messenger = WhatsApp(
        os.getenv("whatsapp_token"),
        phone_number_id=os.getenv("phone_number_id")
    )
except Exception as e:
    logger.error(f"Failed to initialize WhatsApp: {str(e)}")
    raise

VERIFY_TOKEN = "30cca545-3838-48b2-80a7-9e43b1ae8ce4"

# Gemini Configuration
genai.configure(api_key=os.getenv("google_api_key"))
vision_model = genai.GenerativeModel('gemini-2.0-flash-exp')
text_model = genai.GenerativeModel('gemini-2.0-flash-exp')

# Product Data and Cart
try:
    df = pd.read_csv("supermarket_data1.csv")
    product_data = df.to_dict('records')
except Exception as e:
    logger.error(f"Failed to load product data: {str(e)}")
    raise

carts: Dict[str, List[Dict]] = {}

class ShoppingAssistant:
    def __init__(self):
        self.product_data = product_data
        self.last_analyzed_products = {}
    
    def process_input(self, content: Union[str, bytes], content_type: str, mobile: str) -> dict:
        """Multimodal input processor"""
        try:
            if content_type == "text":
                return self._process_text(content, mobile)
            elif content_type == "image":
                return self._process_image(content, mobile)
            else:
                return {"error": "Unsupported content type"}
        except Exception as e:
            logger.error(f"Processing error: {str(e)}")
            return {"error": "Failed to process input"}

    def _process_text(self, text: str, mobile: str) -> dict:
        """Process text input using Gemini"""
        response = text_model.generate_content(
            f"""As a retail shopping assistant, analyze this request and extract shopping context, 
                    specific products needed, pick only one item per product extracted 
                    unless the user specifies the amount or number of items for a particular product. 
                    If the user specifies budget make sure your suggest is within that budget. 

                    If a user suggest a scenario planning a dinner, planning a party, I want to clean my car etc, be analytic and creative
                    and suggest appropriate product
                    Request: {text}"""
        )
        analysis = json.loads(response.text)
        return self._match_products(analysis.get('products', []), mobile)

    def _process_image(self, image_bytes: bytes, mobile: str) -> dict:
        """Process image input using Gemini Vision"""
        img = Image.open(io.BytesIO(image_bytes))
        response = vision_model.generate_content([
            "Analyze this shopping-related image and identify products",
            img
        ])
        analysis = json.loads(response.text)
        return self._match_products(analysis.get('products', []), mobile)

    def _match_products(self, detected_items: List[dict], mobile: str) -> dict:
        """Match detected items with inventory"""
        matched_products = []
        for item in detected_items:
            product = next(
                (p for p in self.product_data 
                 if p['product'].lower() == item['name'].lower()),
                None
            )
            if product:
                matched_products.append({
                    **product,
                    'quantity': item.get('quantity', 1)
                })
        
        if mobile:
            self.last_analyzed_products[mobile] = matched_products
            
        return {
            "products": matched_products,
            "total": sum(p['price']*p.get('quantity',1) for p in matched_products),
            "message": f"Found {len(matched_products)} matching products"
        }

def create_reply_button_message(text: str, buttons: List[Dict]) -> Dict:
    """Create interactive button message"""
    return {
        "type": "button",
        "body": {"text": text},
        "action": {
            "buttons": [
                {
                    "type": "reply",
                    "reply": {
                        "id": btn["id"],
                        "title": btn["title"]
                    }
                } for btn in buttons
            ]
        }
    }

@app.get("/")
def verify_token():
    """Webhook verification"""
    if request.args.get("hub.verify_token") == VERIFY_TOKEN:
        return request.args.get("hub.challenge")
    return "Invalid verification token"

@app.post("/")
def webhook():
    """Main webhook handler"""
    try:
        data = request.get_json()
        mobile = messenger.get_mobile(data)
        message_type = messenger.get_message_type(data)
        assistant = ShoppingAssistant()

        # Handle text input
        if message_type == "text":
            message = messenger.get_message(data)
            result = assistant.process_input(message, "text", mobile)
            
            if result.get('products'):
                products_text = "πŸ›’ *Suggested Products:*\n\n"
                for p in result['products']:
                    products_text += f"➀ {p['product']} x{p.get('quantity',1)}\n   Price: ${p['price']}\n\n"
                products_text += f"πŸ’΅ Total: ${result['total']:.2f}"
                
                buttons = [
                    {"id": "add_to_cart", "title": "πŸ›’ Add to Cart"},
                    {"id": "cancel", "title": "❌ Cancel"}
                ]
                messenger.send_reply_button(
                    mobile,
                    create_reply_button_message(products_text, buttons)
                )
            else:
                messenger.send_message("❌ No matching products found", mobile)

        # Handle image input
        elif message_type == "image":
            image_url = messenger.get_image_url(data)
            response = requests.get(image_url, headers={
                "Authorization": f"Bearer {os.getenv('whatsapp_token')}"
            })
            
            if response.status_code == 200:
                result = assistant.process_input(response.content, "image", mobile)
                
                if result.get('products'):
                    products_text = "πŸ“Έ *Products Found in Your Image:*\n\n"
                    for p in result['products']:
                        products_text += f"➀ {p['product']} x{p.get('quantity',1)}\n   Price: ${p['price']}\n\n"
                    products_text += f"πŸ’΅ Total: ${result['total']:.2f}"
                    
                    buttons = [
                        {"id": "add_to_cart", "title": "πŸ›’ Add to Cart"},
                        {"id": "cancel", "title": "❌ Cancel"}
                    ]
                    messenger.send_reply_button(
                        mobile,
                        create_reply_button_message(products_text, buttons)
                    )
                else:
                    messenger.send_message("❌ No products found in the image", mobile)

        # Handle button interactions
        elif message_type == "interactive":
            response = messenger.get_interactive_response(data)
            button_id = response.get("id")
            
            if button_id == "add_to_cart":
                if mobile in assistant.last_analyzed_products:
                    if mobile not in carts:
                        carts[mobile] = []
                    carts[mobile].extend(assistant.last_analyzed_products[mobile])
                    
                    buttons = [
                        {"id": "continue_shopping", "title": "πŸ›οΈ Continue Shopping"},
                        {"id": "checkout", "title": "πŸ’³ Checkout"}
                    ]
                    messenger.send_reply_button(
                        mobile,
                        create_reply_button_message(
                            "βœ… Items added to cart!\nWhat would you like to do next?",
                            buttons
                        )
                    )
                else:
                    messenger.send_message("❌ No products to add", mobile)
            
            elif button_id == "checkout":
                if mobile in carts and carts[mobile]:
                    total = sum(p['price']*p.get('quantity',1) for p in carts[mobile])
                    receipt = "πŸ“‹ *Your Cart Summary:*\n\n"
                    receipt += "\n".join(
                        f"➀ {p['product']} x{p.get('quantity',1)} - ${p['price']}"
                        for p in carts[mobile]
                    )
                    receipt += f"\n\nπŸ’³ Total: ${total:.2f}"
                    
                    buttons = [
                        {"id": "confirm_checkout", "title": "βœ… Confirm Order"},
                        {"id": "edit_cart", "title": "✏️ Edit Cart"}
                    ]
                    messenger.send_reply_button(
                        mobile,
                        create_reply_button_message(receipt, buttons)
                    )
                else:
                    messenger.send_message("πŸ›’ Your cart is empty!", mobile)

        return "OK", 200

    except Exception as e:
        logger.error(f"Webhook error: {str(e)}")
        return "Internal Server Error", 500

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=7860)